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
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ Behavior and sources are in [the agent guide](../../../../docs/SUBAGENTS.md).

- **Agents:** explorer, executor, validator and reviewer ship in `plugins/aidd-orchestrator/agents/`, installed with the plugin. No tool list is declared: every host gives an agent all tools by default.
- **Translation:** each host receives only the keys its documentation lists: name or file path as the ID, description, model, and Claude's `effort` under the host's own key. Each host has one agent frontmatter function, shared by the plugin install and its built distributions.
- **Out of scope:** per-provider models and a model for the main session.
- **Models:** `aidd translate --provider` pins each role's model and effort from `agent-models.json`; `make reload` passes the provider `scripts/reload-agents.json` names.
- **Out of scope:** the `aidd agents` command, and a model for the main session.

## Checks

- **Claude Code:** the four agents load and run from a project-scoped plugin install.
- **OpenCode:** the four agents run from the flat distribution through `opencode run --standalone --agent <id>` with an OpenAI model, each answering after a `read` call.
- **Codex:** the four agents load from the flat distribution in a trusted project; `codex exec` spawned each, and its session records the agent's role and instructions.
- **Provider models:** with `--provider openai`, OpenCode loaded the executor as `{id: gpt-6-luna, providerID: openai, variant: medium}` (`opencode debug agents`), and a Codex session spawned the explorer on `gpt-6-luna` while its parent ran `gpt-6.1-sol`.
- **Every host:** `cli/tests/e2e/agent-install.e2e.test.ts` builds the repository flat and checks each shipped agent's path, frontmatter and body against the host's documented format.
- **Copilot, Cursor, Kilo:** no host binary in the test environment, so loading by the host is unverified. The Copilot plugin lists agent files where its documentation describes agent directories.
59 changes: 0 additions & 59 deletions cli/src/contexts/tools/domain/formats/agent-frontmatter-strip.ts

This file was deleted.

38 changes: 38 additions & 0 deletions cli/src/contexts/tools/domain/formats/agent-models.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"roles": {
"explorer": "light",
"executor": "light",
"validator": "light",
"reviewer": "standard"
},
"providers": {
"openai": {
"standard": { "model": "gpt-6.1-sol", "effort": "medium" },
"light": { "model": "gpt-6-luna", "effort": "medium" }
},
"anthropic": {
"standard": {
"model": "claude-sonnet-5-5",
"effort": "medium",
"nativeModels": { "copilot": "claude-sonnet-5.5" }
},
"light": {
"model": "claude-haiku-5-5",
"effort": "medium",
"nativeModels": { "copilot": "claude-haiku-5.5" }
}
},
"mistral": {
"standard": { "model": "mistral-medium-latest", "effort": "high" },
"light": { "model": "mistral-small-latest", "effort": "high" }
}
},
"hosts": {
"claude": { "providers": ["anthropic"] },
"codex": { "providers": ["openai"] },
"copilot": { "providers": ["openai", "anthropic"] },
"opencode": { "providers": ["openai", "anthropic", "mistral"], "providerPrefix": true },
"cursor": { "providers": ["anthropic"] },
"kilo": { "providers": ["openai", "anthropic", "mistral"], "providerPrefix": true }
}
}
67 changes: 67 additions & 0 deletions cli/src/contexts/tools/domain/formats/agent-models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { parseFrontmatter, serializeFrontmatter } from "../../../../kernel/markdown.js";
import type { ToolBuildContract } from "../build-contract.js";
import models from "./agent-models.json" with { type: "json" };

type Host = keyof typeof models.hosts;
type Provider = keyof typeof models.providers;

interface ModelProfile {
readonly model: string;
readonly effort: string;
readonly nativeModels?: Readonly<Record<string, string>>;
}

function supportedHost(host: string): Host {
if (!Object.hasOwn(models.hosts, host)) {
throw new Error(`No agent provider is supported for "${host}".`);
}
return host as Host;
}

function supportedProvider(host: Host, provider: string): Provider {
const providers: readonly string[] = models.hosts[host].providers;
if (!providers.includes(provider)) {
throw new Error(
`Provider "${provider}" is not supported for "${host}". Use: ${providers.join(", ")}.`
);
}
return provider as Provider;
}

/** The model and effort an agent's role has at the provider, in the host's model ids. */
function roleModel(host: Host, provider: Provider, role: string): ModelProfile | undefined {
const tier = Object.entries(models.roles).find(([name]) => name === role)?.[1];
const profile: ModelProfile | undefined = Object.entries(models.providers[provider]).find(
([name]) => name === tier
)?.[1];
if (profile === undefined) return undefined;
const prefix = "providerPrefix" in models.hosts[host] ? `${provider}/` : "";
return {
model: `${prefix}${profile.nativeModels?.[host] ?? profile.model}`,
effort: profile.effort,
};
}

/** Gives every agent whose role `agent-models.json` lists its model and effort at the
* provider, before the host writes it. A model the agent declares itself is kept. */
export function withAgentModels(
contract: ToolBuildContract,
host: string,
provider: string
): ToolBuildContract {
const target = supportedHost(host);
const chosen = supportedProvider(target, provider);
const agents = contract.artifacts.agents;
if (!agents.supported) return contract;
const write = agents.transform ?? ((content: string) => content);
const transform = (content: string, plugin: string, fileName: string): string => {
const { frontmatter, body } = parseFrontmatter(content);
const profile = roleModel(target, chosen, fileName.replace(/\.md$/, ""));
if (profile === undefined || frontmatter.model !== undefined) {
return write(content, plugin, fileName);
}
const modelled = serializeFrontmatter({ ...frontmatter, ...profile }, body);
return write(modelled, plugin, fileName);
};
return { ...contract, artifacts: { ...contract.artifacts, agents: { ...agents, transform } } };
}
8 changes: 8 additions & 0 deletions cli/src/presentation/commands/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface TranslateExecutionParams {
target: FrameworkBuildTarget;
mode: FrameworkBuildMode;
force: boolean;
provider?: string;
}

/** The build+report core, once `--to`/`--as`/`--out` flags are validated and resolved. */
Expand All @@ -33,6 +34,7 @@ async function runTranslateCore(params: TranslateExecutionParams): Promise<void>
mode: params.mode,
outDir: params.outDir,
force: params.force,
...(params.provider === undefined ? {} : { provider: params.provider }),
});
if (useCase === undefined) {
params.output.error(
Expand Down Expand Up @@ -61,6 +63,7 @@ interface TranslateCmdOptions {
out: string;
as?: string;
force?: boolean;
provider?: string;
}

export function registerTranslateCommand(program: Command): void {
Expand All @@ -77,6 +80,10 @@ export function registerTranslateCommand(program: Command): void {
.requiredOption("--out <dir>", "Output directory (marketplace dist or project root)")
.option("--as <marketplace|flat>", "Output layout", "marketplace")
.option("--force", "Overwrite existing files at canonical paths under --out")
.option(
"--provider <provider>",
"Give each agent its role's model at this provider (openai, anthropic, mistral)"
)
.action(async (source: string, cmdOptions: TranslateCmdOptions) => {
const { verbose, output, projectRoot } = parseGlobalOptions(program);

Expand Down Expand Up @@ -106,6 +113,7 @@ export function registerTranslateCommand(program: Command): void {
target: cmdOptions.to as FrameworkBuildTarget,
mode,
force: cmdOptions.force ?? false,
...(cmdOptions.provider === undefined ? {} : { provider: cmdOptions.provider }),
});
});
}
11 changes: 9 additions & 2 deletions cli/src/runtime/wiring/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "../../contexts/tools/domain/profiles/kilo/profile.js";
import "../../contexts/tools/domain/profiles/opencode/profile.js";
import "../../contexts/tools/domain/profiles/vscode/profile.js";
import type { ToolBuildContract } from "../../contexts/tools/domain/build-contract.js";
import { withAgentModels } from "../../contexts/tools/domain/formats/agent-models.js";
import type { FileMerger } from "../../contexts/tools/domain/ports/file-merger.js";
import { buildContractFor } from "../../contexts/tools/domain/registry.js";
import { FlatBuildStrategy } from "../../contexts/translate/application/strategies/flat-build-strategy.js";
Expand All @@ -32,6 +33,8 @@ export interface FrameworkBuildContext {
readonly mode: string;
readonly outDir: string;
readonly force: boolean;
/** Gives each agent its role's model at this provider; absent, agents inherit the session's. */
readonly provider?: string;
}

type FrameworkBuildFactory = (
Expand Down Expand Up @@ -68,12 +71,16 @@ function frameworkBuildFactoryFor(
buildContract: () => ToolBuildContract,
mode: "marketplace" | "flat"
): FrameworkBuildFactory {
const contractFor = (ctx: FrameworkBuildContext): ToolBuildContract =>
ctx.provider === undefined
? buildContract()
: withAgentModels(buildContract(), ctx.target, ctx.provider);
if (mode === "marketplace") {
return (deps, ctx) =>
buildFrameworkUseCase(
deps,
(d, av) =>
new MarketplaceBuildStrategy(d.fs, av, d.assetProvider, buildContract(), ctx.force)
new MarketplaceBuildStrategy(d.fs, av, d.assetProvider, contractFor(ctx), ctx.force)
);
}
return (deps, ctx) =>
Expand All @@ -84,7 +91,7 @@ function frameworkBuildFactoryFor(
d.fs,
av,
d.assetProvider,
buildContract(),
contractFor(ctx),
ctx.force,
ctx.outDir,
isDirectory,
Expand Down

This file was deleted.

Loading
Loading