Skip to content
Draft
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
5 changes: 4 additions & 1 deletion src/adapters/openai-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1561,7 +1561,10 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
outBody = promoteClientLoadedTools(outBody);
}
if (provider.authMode !== "forward") {
const rewritten = rewriteRoutedCustomToolsForUpstream(outBody);
const rewritten = rewriteRoutedCustomToolsForUpstream(
outBody,
provider.customToolTransport === "function-json" ? "direct-first" : "legacy",
);
outBody = rewritten.body;
convertedRoutedCustomToolNames = rewritten.names;
}
Expand Down
35 changes: 33 additions & 2 deletions src/adapters/tool-catalog-nudge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,44 @@ export function buildNonOpenAIToolCatalogNudgeFromNames(
);
const verifiedCodeModeExecName = codeModeExecWireName(advertised, codeModeExecName);

// Function-only providers such as Grok can use the direct Codex helpers without composing
// JSON -> JavaScript -> nested helper calls. Keep the old nested-helper guidance for the
// legacy one-tool catalog, but make a projected direct surface explicitly direct-first.
const directEditName = uniqueNames([
"apply_patch", "functions__apply_patch", toWireName("apply_patch"),
]).find(name => advertised.has(name));
const directShellName = uniqueNames([
"exec_command", "shell_command", "functions__exec_command",
toWireName("exec_command"), toWireName("shell_command"),
]).find(name => advertised.has(name));
const directFirst = Boolean(verifiedCodeModeExecName && (directEditName || directShellName));
const directGuidance = directFirst && verifiedCodeModeExecName
? [
"Use a direct listed tool whenever one call completes the operation.",
directEditName ? "Use `" + directEditName + "` directly for targeted edits." : undefined,
directShellName ? "Use `" + directShellName + "` directly for reads, searches, tests, builds, formatters, and genuinely mechanical transformations." : undefined,
"Use `" + verifiedCodeModeExecName + "` only for JavaScript control flow, dependent calls, aggregation, error handling, internal parallelism, or a helper available only inside Code Mode.",
"Emit a real tool call; never print JavaScript or JSON as ordinary text.",
].filter((line): line is string => typeof line === "string").join(" ")
: undefined;

return [
"Tool contract: use the current tool catalog as ground truth.",
"Valid tool names for this turn are exactly " + quoteNames(names) + ".",
"These listed names are the complete top-level tool-call surface for this turn.",
"Call only listed names with their listed argument keys; do not invent, translate, or rename tools.",
"Names mentioned only in instructions, tool descriptions, argument descriptions, or nested helper APIs are not additional top-level tools.",
verifiedCodeModeExecName
? "`" + verifiedCodeModeExecName + "` is Codex code mode: its body is JavaScript evaluated in a V8 isolate. Nested helpers are called INSIDE that body as `await tools.<name>(...)`, for example `await tools.exec_command({cmd: \"ls\"})` or `await tools.codex_app__list_threads({})`. Absence from the top-level catalog or from `" + verifiedCodeModeExecName + "`'s description is not absence: deferred helpers stay callable on `tools.<name>`. Discover them from the isolate global `ALL_TOOLS`, not `tools.ALL_TOOLS`. Do not skip an available nested helper because it is omitted from the listed top-level names."
? directFirst
? directGuidance
: "`" + verifiedCodeModeExecName + "` is Codex code mode: its body is JavaScript evaluated in a V8 isolate. Nested helpers are called INSIDE that body as `await tools.<name>(...)`, for example `await tools.exec_command({cmd: \"ls\"})` or `await tools.codex_app__list_threads({})`. Absence from the top-level catalog or from `" + verifiedCodeModeExecName + "`'s description is not absence: deferred helpers stay callable on `tools.<name>`. Discover them from the isolate global `ALL_TOOLS`, not `tools.ALL_TOOLS`. Do not skip an available nested helper because it is omitted from the listed top-level names."
: "If a listed tool exposes nested helpers such as a tools.* API, call the listed parent tool and use those helpers only inside that tool's input.",
unavailableNeighborNames.length > 0
? "Do not use neighboring-agent tool names " + quoteNames(unavailableNeighborNames) + " unless this turn's catalog lists those exact names."
: undefined,
directEditName
? "Do not use shell redirection, Node, Python, sed, or heredocs for a targeted workspace edit when the direct edit tool is listed; wait for its result before considering any fallback."
: undefined,
"If you need shell, file search, file read, edit, or discovery behavior, choose the listed tool that provides that capability.",
"Count a tool call only after its tool result returns; batch independent read-only calls when the runtime supports it.",
].filter((line): line is string => typeof line === "string").join(" ");
Expand All @@ -141,8 +167,13 @@ export function buildNonOpenAIToolCatalogNudgeForTools(
// to wire names first throws away the only thing that distinguishes Codex's JavaScript
// `exec` from an ordinary structured tool that happens to share the name.
const codeModeExecTool = visible?.find(isCodexCodeModeExecTool);
const hasDirectEditTool = visible?.some(tool => !tool.namespace && tool.name === "apply_patch");
const codeModeExecName = codeModeExecTool
&& !visible?.some(isBareShellBridgeTool)
// A bare shell bridge normally identifies the legacy flat-tool shape rather than Code
// Mode. The hybrid direct-first surface is the intentional exception: its first-class
// apply_patch tool proves that exec and exec_command are being advertised together rather
// than that an ordinary structured shell tool merely happens to be named exec.
&& (!visible?.some(isBareShellBridgeTool) || hasDirectEditTool)
? toWireName(codeModeExecTool)
: undefined;
// Neighbor names are bare and un-namespaced, so probe the same transform with a bare tool.
Expand Down
11 changes: 8 additions & 3 deletions src/codex/catalog/parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,11 @@ export interface CatalogModel {
supportsReasoningSummaries?: boolean;
/**
* Codex tool calling mode for this routed model.
* "code_mode" selects a direct-first routed surface and serializes entry.tool_mode = "code_mode".
* "code_mode_only" (default) sets entry.tool_mode = "code_mode_only".
* "shell" leaves tool_mode unset so Codex declares top-level shell tools (exec_command).
*/
codexToolMode?: "code_mode_only" | "shell";
codexToolMode?: "code_mode" | "code_mode_only" | "shell";
/** Normalized upstream capability names retained for management/API consumers (#485 follow-up). */
capabilities?: string[];
/** OpenCodex-only catalog ownership marker; Codex ignores the serialized extension field. */
Expand Down Expand Up @@ -431,12 +432,16 @@ export const ROUTED_CODEX_TOOL_MODE = "code_mode_only";

export function applyRoutedCodexToolMode(
entry: RawEntry,
toolMode?: "code_mode_only" | "shell" | string,
toolMode?: "code_mode" | "code_mode_only" | "shell" | string,
): RawEntry {
if (toolMode === "shell") {
delete entry.tool_mode;
return entry;
}
if (toolMode === "code_mode") {
entry.tool_mode = "code_mode";
return entry;
}
entry.tool_mode = ROUTED_CODEX_TOOL_MODE;
return entry;
}
Expand Down Expand Up @@ -506,7 +511,7 @@ export function applyMultiAgentMode(
export function normalizeRoutedCatalogEntry(
entry: RawEntry,
parallelToolCalls = false,
toolMode?: "code_mode_only" | "shell" | string,
toolMode?: "code_mode" | "code_mode_only" | "shell" | string,
): RawEntry {
delete entry.model_messages;
delete entry.tool_mode;
Expand Down
17 changes: 14 additions & 3 deletions src/codex/catalog/provider-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
type OAuthActiveTokenObservation,
} from "../../oauth";
import type { OcxConfig, OcxProviderConfig } from "../../types";
import { modelInList } from "../../types";
import { MODEL_ADAPTER_OVERRIDE_ALLOWED, modelInList } from "../../types";
import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider } from "../../generated/model-metadata";
import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
Expand All @@ -37,7 +37,7 @@ import {
serviceTierSupportForModel,
} from "../../providers/service-tier";
import type { FastPolicyAuthority } from "../../providers/fastwire";
import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport } from "../../providers/registry";
import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport, providerModelCustomToolTransport, providerModelWireDefault } from "../../providers/registry";
import { parseAntigravityAvailableModels, registerAntigravityDiscoveredWireModels } from "../../providers/antigravity-models";
import { applyProviderContextCap, providerContextCap, resolveUnknownRoutedContextWindow } from "../../providers/context-cap";
import { routedSlug, slugEquals, slugsEquivalent } from "../../providers/slug-codec";
Expand Down Expand Up @@ -632,7 +632,15 @@ function configuredReasoningSummarySupport(prov: OcxProviderConfig | undefined,
}

export function applyProviderConfigHints(name: string, prov: OcxProviderConfig, model: CatalogModel, providerCap?: number): CatalogModel {
void name;
const configuredWire = prov.modelAdapters?.[model.id];
const defaultWire = providerModelWireDefault(name, prov, model.id, MODEL_ADAPTER_OVERRIDE_ALLOWED, "responses");
const effectiveWire = configuredWire && MODEL_ADAPTER_OVERRIDE_ALLOWED.has(configuredWire)
? configuredWire
: (defaultWire ?? prov.adapter);
const registryMode = effectiveWire === "openai-responses"
&& providerModelCustomToolTransport(name, prov, model.id, "responses") === "function-json"
? "code_mode" as const
: undefined;
const configuredCap = configuredContextWindow(prov, model.id);
const configuredMaxInput = configuredMaxInputTokens(prov, model.id);
let inputModalities = configuredInputModalities(prov, model.id);
Expand Down Expand Up @@ -677,6 +685,9 @@ export function applyProviderConfigHints(name: string, prov: OcxProviderConfig,
...(prov.parallelToolCalls === true || (prov.adapter === "openai-chat" && prov.parallelToolCalls !== false)
? { parallelToolCalls: true }
: {}),
...(registryMode !== undefined && model.codexToolMode === undefined
? { codexToolMode: registryMode }
: {}),
...(prov.codexToolMode !== undefined ? { codexToolMode: prov.codexToolMode } : {}),
};
const capped = applyProviderContextCap(hinted.contextWindow, providerCap);
Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ const providerConfigSchema = z.object({
// undeclared key survives verbatim. A misspelled `codexToolMode` therefore used to be
// accepted, persisted, and then silently resolved to the `code_mode_only` default — the
// operator asked for shell mode, got code mode, and was told nothing (#2106).
// `code_mode` is registry-derived. Persisted config cannot claim that provider capability.
codexToolMode: z.enum(["code_mode_only", "shell"]).optional(),
responsesItemIdRepair: z.object({
message: z.array(z.string().min(1)).optional(),
Expand Down
23 changes: 23 additions & 0 deletions src/providers/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type ModelWireDefault = string | {
authModes?: readonly ProviderAuthKind[];
/** Whether this registry-selected route may relay a caller-owned service_tier. */
forwardCallerServiceTier?: boolean;
customToolTransport?: "freeform" | "function-json";
};

export interface ResponsesTerminalRepairPolicy {
Expand Down Expand Up @@ -1032,12 +1033,14 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
wire: "openai-responses",
inbound: ["responses"],
authModes: ["oauth"],
customToolTransport: "function-json",
forwardCallerServiceTier: false,
},
"grok-4.5": {
wire: "openai-responses",
inbound: ["responses"],
authModes: ["oauth"],
customToolTransport: "function-json",
forwardCallerServiceTier: false,
},
},
Expand Down Expand Up @@ -2754,6 +2757,26 @@ export function providerModelWireDefault(
return wire !== undefined && allowedWires.has(wire) ? wire : undefined;
}

export function providerModelCustomToolTransport(
id: string,
provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
modelId: string,
inbound: InboundWire = "responses",
): "freeform" | "function-json" | undefined {
const entry = getProviderRegistryEntry(id);
if (!entry?.modelWireDefaults) return undefined;
const declared = entry.modelWireDefaults[modelId.trim().toLowerCase()];
if (!declared || typeof declared === "string") return undefined;
if (declared.wire !== "openai-responses" || !declared.inbound.includes(inbound)) return undefined;
const matchesConfiguredTransport = providerMatchesRegistryTransport(id, provider);
const matchesResolvedModelWire = provider.adapter === declared.wire
&& normalizedProviderEndpoint(provider.baseUrl) === normalizedProviderEndpoint(entry.baseUrl);
if (!matchesConfiguredTransport && !matchesResolvedModelWire) return undefined;
const authMode = provider.authMode ?? entry.authKind;
if (declared.authModes && !declared.authModes.includes(authMode)) return undefined;
return declared.customToolTransport;
}

/** Resolve a registry-only upstream-streaming compatibility hint for Responses turns. */
export function providerModelResponsesUpstreamStreaming(
id: string,
Expand Down
Loading
Loading