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
13 changes: 6 additions & 7 deletions src/adapters/openai-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { messagesToChatFormat } from "./openai-chat/messages";
import { withOpenAIChatToolNames } from "./openai-chat/tool-name-registry";
import { openAIChatTransport, stripBracketedModelSuffix } from "./openai-chat/wire";
import { toolChoiceToChatFormat, toolsToChatFormatForProvider } from "./openai-chat/tool-schema";
import { reconcileSerializedToolCallEvents, reconcileStructuredToolCall, reconcileStructuredToolCalls, SerializedToolCallContentBuffer } from "./openai-chat/serialized-tool-call-content";
import { freeformToolsByWireName, type FreeformToolIdentity, reconcileSerializedToolCallEvents, reconcileStructuredToolCall, reconcileStructuredToolCalls, SerializedToolCallContentBuffer } from "./openai-chat/serialized-tool-call-content";

export { stripBracketedModelSuffix } from "./openai-chat/wire";
export { buildOpenAIChatPassthroughRequest } from "./openai-chat/passthrough";
Expand Down Expand Up @@ -93,7 +93,7 @@ function canSerializeOpenAIChatServiceTier(

export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAdapter {
let lastRequestedModelId: string | undefined;
let freeformToolsByWireName = new Map<string, { name: string; namespace?: string }>();
let freeformTools = new Map<string, FreeformToolIdentity>();
return withOpenAIChatToolNames(toolNames => ({
name: "openai-chat",

Expand All @@ -103,8 +103,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
lastRequestedModelId = parsed.modelId;
const { url, headers, hasCredential } = openAIChatTransport(provider);
const messages = toolNames.messages(parsed, provider.baseUrl, messagesToChatFormat(parsed, provider));
freeformToolsByWireName = new Map(parsed.context.tools?.filter(tool => tool.freeform)
.map(tool => [toolNames.registry().alias(tool), { name: tool.name, namespace: tool.namespace }] as const) ?? []);
freeformTools = freeformToolsByWireName(parsed.context.tools, tool => toolNames.registry().alias(tool));
const finish = (): AdapterRequest => {
const tools = toolsToChatFormatForProvider(parsed, provider, toolNames.registry());
const toolChoice = toolChoiceToChatFormat(parsed.options.toolChoice, parsed.context.tools, provider, toolNames.registry());
Expand Down Expand Up @@ -341,7 +340,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
}
}
// Held markup is released only now, as one batch per response: the doubled-input repair needs every call.
const references = reconcileStructuredToolCalls(calls.map(call => ({ wireName: call.name, restoredName: toolNames.restore(call.name), argumentsText: call.args, freeformTool: freeformToolsByWireName.get(call.name) })), toolCallContent.current());
const references = reconcileStructuredToolCalls(calls.map(call => ({ wireName: call.name, restoredName: toolNames.restore(call.name), argumentsText: call.args, freeformTool: freeformTools.get(call.name) })), toolCallContent.current());
calls.forEach((call, index) => { call.args = references[index]!.argumentsText; });
yield* toolCallContent.drain(references);
for (const call of calls) {
Expand Down Expand Up @@ -776,7 +775,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
const contentEnd = events.length;
const answerText = events.slice(contentStart).map(event => (event.type === "text_delta" ? event.text : "")).join("");
// Each call holds the delta event it emitted, so the batch repair sets its arguments later.
const structuredCalls: { wireName: string; restoredName: string; argumentsText: string; freeformTool?: { name: string; namespace?: string }; delta: Extract<AdapterEvent, { type: "tool_call_delta" }> }[] = [];
const structuredCalls: { wireName: string; restoredName: string; argumentsText: string; freeformTool?: FreeformToolIdentity; delta: Extract<AdapterEvent, { type: "tool_call_delta" }> }[] = [];
const rawToolCalls = msg.tool_calls;
if (rawToolCalls !== undefined && rawToolCalls !== null) {
if (!Array.isArray(rawToolCalls)) {
Expand All @@ -800,7 +799,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
return [invalidToolCallsEvent(rawToolCalls, "response", usage)];
}
const delta: Extract<AdapterEvent, { type: "tool_call_delta" }> = { type: "tool_call_delta", arguments: args };
structuredCalls.push({ wireName: name, restoredName: toolNames.restore(name), argumentsText: args, freeformTool: freeformToolsByWireName.get(name), delta });
structuredCalls.push({ wireName: name, restoredName: toolNames.restore(name), argumentsText: args, freeformTool: freeformTools.get(name), delta });
events.push({ type: "tool_call_start", id, name: toolNames.restore(name) }, delta, { type: "tool_call_end" });
}
}
Expand Down
20 changes: 17 additions & 3 deletions src/adapters/openai-chat/serialized-tool-call-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,24 @@ export interface SerializedToolCall {
end: number;
}

/** A freeform tool's declared identity. Echoes are matched against it, never against the wire alias. */
export interface FreeformToolIdentity {
name: string;
namespace?: string;
}

/** The request's freeform tools keyed by the wire name the provider sees for each of them. */
export function freeformToolsByWireName<T extends FreeformToolIdentity & { freeform?: boolean }>(
tools: readonly T[] | undefined,
wireName: (tool: T) => string,
): Map<string, FreeformToolIdentity> {
return new Map(tools?.filter(tool => tool.freeform).map(tool => [wireName(tool), { name: tool.name, namespace: tool.namespace }] as const) ?? []);
}

export interface StructuredToolCallReference {
names: ReadonlySet<string>;
argumentsText: string;
freeformTool?: { name: string; namespace?: string };
freeformTool?: FreeformToolIdentity;
}

const BLOCK_HEADER = /<tool_call>\s*<function=([^>\r\n]+)>/y;
Expand Down Expand Up @@ -327,7 +341,7 @@ export class SerializedToolCallContentBuffer {
/** Reads a wrapped input or raw arguments from a declared freeform tool; neither path rewrites them. */
function inputFromArguments(
argumentsText: string,
freeformTool?: { name: string; namespace?: string },
freeformTool?: FreeformToolIdentity,
): string | undefined {
try {
const parsed = JSON.parse(argumentsText) as unknown;
Expand Down Expand Up @@ -481,7 +495,7 @@ export interface StructuredToolCallInput {
wireName: string;
restoredName: string;
argumentsText: string;
freeformTool?: { name: string; namespace?: string };
freeformTool?: FreeformToolIdentity;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test";
import { createOpenAIChatAdapter } from "../../../src/adapters/openai-chat";
import { SerializedToolCallContentBuffer } from "../../../src/adapters/openai-chat/serialized-tool-call-content";
import { freeformToolsByWireName, SerializedToolCallContentBuffer } from "../../../src/adapters/openai-chat/serialized-tool-call-content";
import type { AdapterEvent } from "../../../src/types";
import { createTestTranslatorBudget, withTestTranslatorBudget } from "../../helpers/translator-budget";

Expand Down Expand Up @@ -658,3 +658,17 @@ describe("MiMo echo variants (#5724)", () => {
expect(events.filter(event => event.type === "tool_call_start")).toHaveLength(2);
});
});

test("freeformToolsByWireName keys only freeform tools by their wire name and keeps the declared identity", () => {
const tools = [
{ name: "exec", namespace: "functions", freeform: true },
{ name: "read_file", freeform: false },
{ name: "apply_patch", freeform: true },
];
const map = freeformToolsByWireName(tools, tool => `wire_${tool.name}`);
expect([...map.entries()]).toEqual([
["wire_exec", { name: "exec", namespace: "functions" }],
["wire_apply_patch", { name: "apply_patch", namespace: undefined }],
]);
expect(freeformToolsByWireName(undefined, () => "unused").size).toBe(0);
});
Loading