Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/backends/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const LLMIST_ENGINE_DEFINITION: AgentEngineDefinition = {
id: 'llmist',
label: 'LLMist',
description: 'LLMist SDK with synthetic tool context and CASCADE gadget support.',
archetype: 'sdk',
capabilities: [
'synthetic_tool_context',
'streaming_text_events',
Expand All @@ -21,6 +22,7 @@ export const CLAUDE_CODE_ENGINE_DEFINITION: AgentEngineDefinition = {
id: 'claude-code',
label: 'Claude Code',
description: 'Anthropic Claude Code SDK with built-in file tools and Bash-driven CASCADE tools.',
archetype: 'native-tool',
capabilities: [
'inline_prompt_context',
'offloaded_context_files',
Expand Down Expand Up @@ -80,6 +82,7 @@ export const CODEX_ENGINE_DEFINITION: AgentEngineDefinition = {
id: 'codex',
label: 'Codex',
description: 'OpenAI Codex CLI in headless automation mode with CASCADE tool guidance.',
archetype: 'native-tool',
capabilities: [
'inline_prompt_context',
'offloaded_context_files',
Expand Down Expand Up @@ -145,6 +148,7 @@ export const OPENCODE_ENGINE_DEFINITION: AgentEngineDefinition = {
id: 'opencode',
label: 'OpenCode',
description: 'OpenCode headless agent server with scoped permissions and CASCADE tool guidance.',
archetype: 'native-tool',
capabilities: [
'inline_prompt_context',
'offloaded_context_files',
Expand Down
2 changes: 2 additions & 0 deletions src/backends/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export {
getEngine,
getEngineCatalog,
getRegisteredEngines,
isNativeToolEngine,
isNativeToolEngineDefinition,
registerEngine,
} from './registry.js';
export { registerBuiltInEngines } from './bootstrap.js';
Expand Down
16 changes: 16 additions & 0 deletions src/backends/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,19 @@ export function getRegisteredEngines(): string[] {
export function getEngineCatalog(): AgentEngineDefinition[] {
return Array.from(engines.values()).map((engine) => engine.definition);
}

/**
* Returns true if the given engine definition has the 'native-tool' archetype.
*/
export function isNativeToolEngineDefinition(def: AgentEngineDefinition): boolean {
return def.archetype === 'native-tool';
}

/**
* Returns true if the engine with the given ID is registered and has the 'native-tool' archetype.
*/
export function isNativeToolEngine(engineId: string): boolean {
const engine = engines.get(engineId);
if (!engine) return false;
return isNativeToolEngineDefinition(engine.definition);
}
5 changes: 3 additions & 2 deletions src/backends/secretOrchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { AgentInput, CascadeConfig, ProjectConfig } from '../types/index.js
import { getDashboardUrl } from '../utils/runLink.js';
import { createNativeToolRuntimeArtifacts } from './nativeToolRuntime.js';
import { isGitHubAckComment } from './progressLifecycle.js';
import { isNativeToolEngineDefinition } from './registry.js';
import {
augmentProjectSecrets,
injectGitHubAckCommentId,
Expand All @@ -34,7 +35,7 @@ export async function buildExecutionPlan(
_log: ReturnType<typeof createAgentLogger>,
gitHubToken: string | undefined,
isGitHubAck: boolean,
engineId: string,
_engineId: string,
engine: AgentEngine,
): Promise<
Omit<AgentExecutionPlan, 'progressReporter'> & {
Expand Down Expand Up @@ -106,7 +107,7 @@ export async function buildExecutionPlan(
});

const cliToolsDir = new URL('../../bin', import.meta.url).pathname;
const needsNativeToolRuntime = ['claude-code', 'codex', 'opencode'].includes(engineId);
const needsNativeToolRuntime = isNativeToolEngineDefinition(engine.definition);
const nativeToolRuntime = needsNativeToolRuntime ? createNativeToolRuntimeArtifacts() : undefined;

// Build per-project secrets with CASCADE env var injections
Expand Down
1 change: 1 addition & 0 deletions src/backends/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export interface AgentEngineDefinition {
readonly id: string;
readonly label: string;
readonly description: string;
readonly archetype: 'sdk' | 'native-tool';
readonly capabilities: string[];
readonly modelSelection:
| { type: 'free-text' }
Expand Down
1 change: 1 addition & 0 deletions tests/unit/agents/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function makeMockEngine(id: string, supportsAll = true): AgentEngine {
id,
label: id,
description: `${id} description`,
archetype: 'sdk',
capabilities: [],
modelSelection: { type: 'free-text' },
logLabel: 'Engine Log',
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/api/routers/agentConfigs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ describe('agentConfigsRouter', () => {
id: 'llmist',
label: 'LLMist',
description: 'LLMist',
archetype: 'sdk',
capabilities: [],
modelSelection: { type: 'free-text' },
logLabel: 'LLMist Log',
Expand All @@ -101,6 +102,7 @@ describe('agentConfigsRouter', () => {
id: 'claude-code',
label: 'Claude Code',
description: 'Claude Code',
archetype: 'native-tool',
capabilities: [],
modelSelection: {
type: 'select',
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/backends/adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,13 @@ function makeInput(
}

function makeMockBackend(id = 'test-engine'): AgentEngine {
const nativeToolIds = ['claude-code', 'codex', 'opencode'];
return {
definition: {
id,
label: 'Test Engine',
description: 'Test engine',
archetype: nativeToolIds.includes(id) ? 'native-tool' : 'sdk',
capabilities: [],
modelSelection: { type: 'free-text' },
logLabel: 'Engine Log',
Expand Down
32 changes: 32 additions & 0 deletions tests/unit/backends/catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ describe('LLMIST_ENGINE_DEFINITION', () => {
expect(LLMIST_ENGINE_DEFINITION.label).toBe('LLMist');
});

it('has sdk archetype', () => {
expect(LLMIST_ENGINE_DEFINITION.archetype).toBe('sdk');
});

it('has free-text model selection', () => {
expect(LLMIST_ENGINE_DEFINITION.modelSelection.type).toBe('free-text');
});
Expand All @@ -78,6 +82,10 @@ describe('CLAUDE_CODE_ENGINE_DEFINITION', () => {
expect(CLAUDE_CODE_ENGINE_DEFINITION.label).toBe('Claude Code');
});

it('has native-tool archetype', () => {
expect(CLAUDE_CODE_ENGINE_DEFINITION.archetype).toBe('native-tool');
});

it('has select model selection with default label', () => {
expect(CLAUDE_CODE_ENGINE_DEFINITION.modelSelection.type).toBe('select');
if (CLAUDE_CODE_ENGINE_DEFINITION.modelSelection.type === 'select') {
Expand Down Expand Up @@ -130,6 +138,10 @@ describe('CODEX_ENGINE_DEFINITION', () => {
expect(CODEX_ENGINE_DEFINITION.label).toBe('Codex');
});

it('has native-tool archetype', () => {
expect(CODEX_ENGINE_DEFINITION.archetype).toBe('native-tool');
});

it('has select model selection with default label', () => {
expect(CODEX_ENGINE_DEFINITION.modelSelection.type).toBe('select');
if (CODEX_ENGINE_DEFINITION.modelSelection.type === 'select') {
Expand Down Expand Up @@ -173,6 +185,10 @@ describe('OPENCODE_ENGINE_DEFINITION', () => {
expect(OPENCODE_ENGINE_DEFINITION.label).toBe('OpenCode');
});

it('has native-tool archetype', () => {
expect(OPENCODE_ENGINE_DEFINITION.archetype).toBe('native-tool');
});

it('has free-text model selection', () => {
expect(OPENCODE_ENGINE_DEFINITION.modelSelection.type).toBe('free-text');
});
Expand Down Expand Up @@ -202,6 +218,22 @@ describe('OPENCODE_ENGINE_DEFINITION', () => {

// ─── Cross-cutting properties ─────────────────────────────────────────────────
describe('Engine definitions cross-cutting properties', () => {
it('all engines have a valid archetype value', () => {
for (const engine of DEFAULT_ENGINE_CATALOG) {
expect(['sdk', 'native-tool']).toContain(engine.archetype);
}
});

it('only llmist has sdk archetype', () => {
for (const engine of DEFAULT_ENGINE_CATALOG) {
if (engine.id === 'llmist') {
expect(engine.archetype).toBe('sdk');
} else {
expect(engine.archetype).toBe('native-tool');
}
}
});

it('all engines have scoped_env_secrets capability', () => {
for (const engine of DEFAULT_ENGINE_CATALOG) {
expect(engine.capabilities).toContain('scoped_env_secrets');
Expand Down
9 changes: 9 additions & 0 deletions tests/unit/backends/engine-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ describe.each(EXPECTED_ENGINE_IDS)('engine: %s', (engineId) => {
expect(typeof definition.description).toBe('string');
expect(definition.description.length).toBeGreaterThan(0);

expect(['sdk', 'native-tool']).toContain(definition.archetype);

expect(Array.isArray(definition.capabilities)).toBe(true);

expect(definition.modelSelection).toBeDefined();
Expand All @@ -60,6 +62,13 @@ describe.each(EXPECTED_ENGINE_IDS)('engine: %s', (engineId) => {
expect(definition.logLabel.length).toBeGreaterThan(0);
});

it('has archetype set to sdk or native-tool', () => {
const engine = getEngine(engineId);
expect(engine).toBeDefined();
if (!engine) return;
expect(['sdk', 'native-tool']).toContain(engine.definition.archetype);
});

it("definition.id matches the engine's registry key", () => {
const engine = getEngine(engineId);
expect(engine).toBeDefined();
Expand Down
1 change: 1 addition & 0 deletions tests/unit/backends/postProcess.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function makeEngine(id = 'test-engine'): AgentEngine {
id,
label: id,
description: `${id} description`,
archetype: 'sdk',
capabilities: [],
modelSelection: { type: 'free-text' },
logLabel: 'Engine Log',
Expand Down
39 changes: 37 additions & 2 deletions tests/unit/backends/registry.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { beforeEach, describe, expect, it } from 'vitest';
import { getEngine, getRegisteredEngines, registerEngine } from '../../../src/backends/registry.js';
import {
getEngine,
getRegisteredEngines,
isNativeToolEngine,
isNativeToolEngineDefinition,
registerEngine,
} from '../../../src/backends/registry.js';
import type { AgentEngine } from '../../../src/backends/types.js';

function createMockEngine(id: string): AgentEngine {
function createMockEngine(id: string, archetype: 'sdk' | 'native-tool' = 'sdk'): AgentEngine {
return {
definition: {
id,
label: id,
description: `${id} description`,
archetype,
capabilities: [],
modelSelection: { type: 'free-text' },
logLabel: 'Engine Log',
Expand Down Expand Up @@ -57,3 +64,31 @@ describe('getRegisteredEngines', () => {
expect(names).toContain('test-list-b');
});
});

describe('isNativeToolEngineDefinition', () => {
it('returns true for native-tool archetype', () => {
const engine = createMockEngine('test-native-def', 'native-tool');
expect(isNativeToolEngineDefinition(engine.definition)).toBe(true);
});

it('returns false for sdk archetype', () => {
const engine = createMockEngine('test-sdk-def', 'sdk');
expect(isNativeToolEngineDefinition(engine.definition)).toBe(false);
});
});

describe('isNativeToolEngine', () => {
it('returns true for a registered native-tool engine', () => {
registerEngine(createMockEngine('test-native-reg', 'native-tool'));
expect(isNativeToolEngine('test-native-reg')).toBe(true);
});

it('returns false for a registered sdk engine', () => {
registerEngine(createMockEngine('test-sdk-reg', 'sdk'));
expect(isNativeToolEngine('test-sdk-reg')).toBe(false);
});

it('returns false for an unknown engine id', () => {
expect(isNativeToolEngine('nonexistent-engine-abc')).toBe(false);
});
});
Loading