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
2 changes: 1 addition & 1 deletion scripts/dead-export-guard.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"tsconfig": "tsconfig.json",
"minScannedFiles": 1240
"minScannedFiles": 1366
}
55 changes: 46 additions & 9 deletions src/agent/prompt-sizes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,15 +204,33 @@ describe("director prompt size budget", () => {
expect(new Set(names).size, `${directorId} [${family}]`).toBe(
names.length,
);
// Hidden Codex aliases (apply_patch/shell/update_plan) are not advertised,
// as must list_dir, which no subagent mount installs.
for (const phantom of [
"list_dir",
"apply_patch",
"shell",
"update_plan",
]) {
expect(names, `${directorId} [${family}]`).not.toContain(phantom);
// list_dir is not installed on a subagent mount. apply_patch, shell,
// and update_plan are the gpt wire names — other families keep the
// default wires.
expect(names, `${directorId} [${family}]`).not.toContain("list_dir");
if (family === "gpt") {
const defaults = canonicalToolNamesForDirector(
DIRECTOR_REGISTRY[directorId],
"default",
);
expect(names.includes("apply_patch"), directorId).toBe(
defaults.includes("write"),
);
expect(names.includes("shell"), directorId).toBe(
defaults.includes("bash"),
);
expect(names.includes("update_plan"), directorId).toBe(
defaults.includes("todowrite"),
);
expect(names, directorId).not.toContain("write");
expect(names, directorId).not.toContain("edit");
expect(names, directorId).not.toContain("delete");
expect(names, directorId).not.toContain("bash");
expect(names, directorId).not.toContain("todowrite");
} else {
for (const phantom of ["apply_patch", "shell", "update_plan"]) {
expect(names, `${directorId} [${family}]`).not.toContain(phantom);
}
}
if (DIRECTOR_REGISTRY[directorId].spawn.maySpawn) {
for (const verb of ["spawn_agent", "send_input"]) {
Expand All @@ -225,6 +243,25 @@ describe("director prompt size budget", () => {
}
}
});

test("gpt shakespeare lists apply_patch, not write/edit/delete", () => {
const names = canonicalToolNamesForDirector(
DIRECTOR_REGISTRY.shakespeare,
"gpt",
);
expect(names).toContain("apply_patch");
expect(names).not.toContain("write");
expect(names).not.toContain("edit");
expect(names).not.toContain("delete");
expect(names).not.toContain("write_file");
expect(names).not.toContain("shell");
const defaults = canonicalToolNamesForDirector(
DIRECTOR_REGISTRY.shakespeare,
"default",
);
expect(defaults).not.toContain("apply_patch");
expect(defaults).toContain("write");
});
});

describe("dispatch grok prefix (infer envelope vs trimmed director)", () => {
Expand Down
14 changes: 11 additions & 3 deletions src/agent/prompt-sizes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import {
import { buildSubAgentSystemPrompt } from "./prompts.js";
import { resolveModelFamilyPolicy } from "./model-family-policy.js";
import { shouldApplyGrokAntiThrash } from "../subagent/provider-family.js";
import { advertisedToolName } from "./tool-aliases.js";
import {
advertisedToolName,
foldFileToolNames,
toolProfileForFamily,
} from "./tool-aliases.js";
import { canonicalToolName } from "./canonical-tool-name.js";
import { manageTasksDefinition } from "./tasks.js";
import { DELETE_FILE_DEFINITION } from "../plugins/delete-file-plugin.js";
Expand Down Expand Up @@ -86,7 +90,7 @@ function preFilterMountNames(): readonly string[] {
*/
export function canonicalToolNamesForDirector(
pkg: DirectorPackage,
_family: PromptSizeFamily,
family: PromptSizeFamily,
): readonly string[] {
const filtered = [...preFilterMountNames()];
const capabilities = packageToCapabilities(pkg);
Expand Down Expand Up @@ -130,7 +134,11 @@ export function canonicalToolNamesForDirector(
`canonicalToolNamesForDirector(${pkg.id}): "${dupe}" mounted twice — the assembly drifted from src/subagent/run.ts`,
);
}
return names.map((name) => advertisedToolName(name));
const profile = toolProfileForFamily(family);
return foldFileToolNames(
names.map((name) => advertisedToolName(name, profile)),
profile,
);
}

/** Assemble one director prompt exactly as run.ts does. */
Expand Down
117 changes: 102 additions & 15 deletions src/agent/tool-aliases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,37 +42,117 @@ const ENGINE_TO_WIRE_BY_PROFILE: Record<
gpt: { ...DEFAULT_ENGINE_TO_WIRE, ...GPT_OVERRIDES },
};

/** Tools the gpt profile folds into the single apply_patch envelope tool. */
const PATCH_FOLDED_ENGINES: ReadonlySet<string> = new Set([
"write_file",
"edit_file",
"delete_file",
]);
/**
* Engines one advertised wire replaces. A profile without an entry keeps
* the 1:1 wire table. Adding a family is a new ToolProfile plus a row here
* and in ENGINE_TO_WIRE_BY_PROFILE — not a new director package.
*/
const PROFILE_FOLDS: Record<
ToolProfile,
{ wire: string; engines: readonly string[] } | undefined
> = {
default: undefined,
gpt: {
wire: "apply_patch",
engines: ["write_file", "edit_file", "delete_file"],
},
};

function buildAdvertisedWireEngines(
profile: ToolProfile,
): ReadonlyMap<string, readonly string[]> {
const fold = PROFILE_FOLDS[profile];
const folded = new Set(fold?.engines ?? []);
const map = new Map<string, string[]>();
for (const [engine, wire] of Object.entries(
ENGINE_TO_WIRE_BY_PROFILE[profile],
)) {
if (folded.has(engine)) continue;
const existing = map.get(wire);
if (existing === undefined) map.set(wire, [engine]);
else existing.push(engine);
}
if (fold !== undefined) map.set(fold.wire, [...fold.engines]);
return map;
}

const ADVERTISED_WIRE_ENGINES = Object.fromEntries(
(Object.keys(ENGINE_TO_WIRE_BY_PROFILE) as ToolProfile[]).map((profile) => [
profile,
buildAdvertisedWireEngines(profile),
]),
) as Record<ToolProfile, ReadonlyMap<string, readonly string[]>>;

export function toolProfiles(): readonly ToolProfile[] {
return Object.keys(ENGINE_TO_WIRE_BY_PROFILE) as ToolProfile[];
}

/** Wire name → engine ids this profile advertises. Folded wires map to every engine they replace. */
export function advertisedWireEngines(
profile: ToolProfile,
): ReadonlyMap<string, readonly string[]> {
return ADVERTISED_WIRE_ENGINES[profile];
}

/**
* Engines `wire` stands for on `profile`. Undefined when this profile does
* not advertise that name (a later family follows the same table).
*/
export function enginesForAdvertisedWire(
wire: string,
profile: ToolProfile,
): readonly string[] | undefined {
const table = ADVERTISED_WIRE_ENGINES[profile];
return table.get(wire) ?? table.get(wire.toLowerCase());
}

/** Wire this profile advertises for `engine`, including a fold that replaces it. */
function profileWireName(engine: string, profile: ToolProfile): string {
const fold = PROFILE_FOLDS[profile];
if (fold !== undefined && fold.engines.includes(engine)) return fold.wire;
return advertisedToolName(engine, profile);
}

/** Every wire any profile advertises for `engine`, including fold wires. */
export function advertisedNamesForEngine(engine: string): readonly string[] {
const names: string[] = [];
const seen = new Set<string>();
for (const profile of toolProfiles()) {
const wire = profileWireName(engine, profile);
if (seen.has(wire)) continue;
seen.add(wire);
names.push(wire);
}
return names;
}

/** gpt models are trained on apply_patch, not write/edit/delete. Other
* profiles and lists without a file-mutation tool pass through unchanged. */
* profiles and lists without a folded tool pass through unchanged. */
export function foldFileToolNames(
names: readonly string[],
profile: ToolProfile,
): readonly string[] {
if (profile !== "gpt") return names;
const fold = PROFILE_FOLDS[profile];
if (fold === undefined) return names;
const folded = (name: string): boolean =>
PATCH_FOLDED_ENGINES.has(engineToolName(name));
fold.engines.includes(engineToolName(name));
if (!names.some(folded)) return names;
const first = names.findIndex(folded);
return names.flatMap((name, i) =>
i === first ? ["apply_patch"] : folded(name) ? [] : [name],
i === first ? [fold.wire] : folded(name) ? [] : [name],
);
}

export function foldFileToolDefinitions(
defs: readonly ToolDefinition[],
profile: ToolProfile,
): ToolDefinition[] {
if (profile !== "gpt" || !defs.some((d) => d.name === "apply_patch")) {
const fold = PROFILE_FOLDS[profile];
if (fold === undefined || !defs.some((d) => d.name === fold.wire)) {
return [...defs];
}
return defs.filter((d) => !PATCH_FOLDED_ENGINES.has(engineToolName(d.name)));
const folded = new Set(fold.engines);
return defs.filter((d) => !folded.has(engineToolName(d.name)));
}

export function toolProfileForFamily(family: ModelFamily): ToolProfile {
Expand Down Expand Up @@ -128,16 +208,23 @@ export function advertisedToolName(
}

/** True when `name` (wire, engine, or hidden alias) is covered by an
* advertised or activated listing (which may store wire or engine ids). */
* advertised or activated listing (which may store wire or engine ids).
* A call is advertised when this profile's wire is on the list, and also
* when the default wire is — primary prefixes still store `bash` while a
* gpt worker prefix stores `shell`. */
export function nameMatchesAdvertisedListing(
name: string,
isListed: (candidate: string) => boolean,
profile: ToolProfile = "default",
): boolean {
if (isListed(name)) return true;
const engine = engineToolName(name);
if (engine !== name && isListed(engine)) return true;
const wire = advertisedToolName(engine);
return wire !== name && isListed(wire);
const wires =
profile === "default"
? [profileWireName(engine, profile)]
: [profileWireName(engine, profile), profileWireName(engine, "default")];
return wires.some((wire) => wire !== name && isListed(wire));
}

// update_plan dispatches through translateUpdatePlanArgs, so advertising
Expand Down
116 changes: 116 additions & 0 deletions src/agent/tool-search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
ORCHESTRATOR_TOOLS,
READ_TOOLS,
} from "./directors/tool-sets.js";
import { advertisedWireEngines, toolProfiles } from "./tool-aliases.js";

const FULL_AVAILABILITY: ToolAvailability = {
languageServerAvailable: true,
Expand Down Expand Up @@ -1010,6 +1011,121 @@ describe("advertisedToolNamesForWorker", () => {
expect(docs).toContain("tool_search");
});

test("gpt docs surface folds file tools into apply_patch", () => {
const docs = advertisedToolNamesForWorker({
allow: DOCS_TOOLS,
profile: "gpt",
});
expect(docs).toContain("apply_patch");
expect(docs).not.toContain("write_file");
expect(docs).not.toContain("edit_file");
expect(docs).not.toContain("delete_file");
expect(docs).not.toContain("write");
expect(docs).not.toContain("edit");
expect(docs).not.toContain("delete");
expect(docs).not.toContain("shell");
expect(docs).not.toContain("run_shell");
});

test("default docs surface does not advertise apply_patch", () => {
const docs = advertisedToolNamesForWorker({
allow: DOCS_TOOLS,
profile: "default",
});
expect(docs).not.toContain("apply_patch");
expect(docs).toContain("write_file");
expect(docs).toContain("edit_file");
expect(docs).toContain("delete_file");
});

test("gpt build surface renames shell and folds file tools", () => {
const build = advertisedToolNamesForWorker({
allow: BUILD_TOOLS,
profile: "gpt",
});
expect(build).toContain("apply_patch");
expect(build).toContain("shell");
expect(build).not.toContain("bash");
expect(build).not.toContain("run_shell");
expect(build).not.toContain("write");
expect(build).not.toContain("write_file");
});

test("gpt shell is advertised, so a shell call does not promote run_shell", async () => {
const prefix = advertisedToolNamesForWorker({
allow: BUILD_TOOLS,
profile: "gpt",
});
const runner = createDynamicToolRunner([
{
kind: "string",
definition: {
name: "run_shell",
description: "run a shell command",
inputSchema: { type: "object", properties: {}, required: [] },
},
handler: async () => "ran:shell",
},
{
kind: "string",
definition: {
name: "apply_patch",
description: "apply a patch",
inputSchema: { type: "object", properties: {}, required: [] },
},
handler: async () => "ran:patch",
},
]);
const advertised = createAdvertisedToolset({
sessionMode: "orchestrator",
toolAvailability: { languageServerAvailable: false },
getProvider: () => ({ providerName: "openai", model: "gpt-5-codex" }),
builtInPrefix: prefix,
});
expect(advertised.isAdvertised("shell")).toBe(true);
expect(advertised.isAdvertised("run_shell")).toBe(true);
expect(advertised.isAdvertised("apply_patch")).toBe(true);
const promoted: string[] = [];
runner.setCallGate((name) => advertised.isAdvertised(name));
runner.setOnUndeclaredCall((name) => {
promoted.push(name);
advertised.activated.activate([name]);
});
const signal = new AbortController().signal;
const shell = await runner.run(
{ id: "shell", name: "shell", arguments: { command: "true" } },
signal,
);
const patch = await runner.run(
{ id: "patch", name: "apply_patch", arguments: {} },
signal,
);
expect(shell.content).toBe("ran:shell");
expect(shell.isError).toBeUndefined();
expect(patch.content).toBe("ran:patch");
expect(patch.isError).toBeUndefined();
expect(promoted).toEqual([]);
});

test("every non-default profile projects allowlisted engines through its wire table", () => {
const allow = BUILD_TOOLS as readonly string[];
for (const profile of toolProfiles()) {
const names = advertisedToolNamesForWorker({
allow: BUILD_TOOLS,
profile,
});
if (profile === "default") {
expect(names).toContain("write_file");
expect(names).not.toContain("apply_patch");
continue;
}
for (const [wire, engines] of advertisedWireEngines(profile)) {
if (!engines.some((engine) => allow.includes(engine))) continue;
expect(names, `${profile} ${wire}`).toContain(wire);
}
}
});

test("nested orchestrator allowlist adds fleet verbs but not search_agents", () => {
const orch = advertisedToolNamesForWorker({ allow: ORCHESTRATOR_TOOLS });
expect(orch).toContain("spawn_agent");
Expand Down
Loading
Loading