fix umans escaped tool roundtrip
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Anthropic-compatible Umans requests escaping client `web_search` tools so Kimi answers normally instead of returning raw gateway search results.
|
||||
- Fixed Anthropic-compatible Umans requests escaping client tool names so Kimi answers normally instead of returning raw gateway search results.
|
||||
|
||||
## [16.0.1] - 2026-06-15
|
||||
|
||||
|
||||
@@ -758,18 +758,6 @@ function isUmansGatewayWebSearchEnabled(
|
||||
return isUmansAnthropicModel(model) && getUmansWebSearchProvider(headers) !== undefined;
|
||||
}
|
||||
|
||||
function shouldEscapeAnthropicBuiltinToolName(
|
||||
name: string,
|
||||
escapeBuiltinToolNames: boolean,
|
||||
useUmansGatewayWebSearch: boolean,
|
||||
): boolean {
|
||||
return (
|
||||
escapeBuiltinToolNames &&
|
||||
ANTHROPIC_BUILTIN_TOOL_NAMES.has(name.toLowerCase()) &&
|
||||
!shouldUseUmansGatewayWebSearch(name, useUmansGatewayWebSearch)
|
||||
);
|
||||
}
|
||||
|
||||
function encodeAnthropicToolName(
|
||||
name: string,
|
||||
isOAuthToken: boolean,
|
||||
@@ -777,17 +765,13 @@ function encodeAnthropicToolName(
|
||||
useUmansGatewayWebSearch = false,
|
||||
): string {
|
||||
if (shouldUseUmansGatewayWebSearch(name, useUmansGatewayWebSearch)) return name;
|
||||
if (shouldEscapeAnthropicBuiltinToolName(name, escapeBuiltinToolNames, useUmansGatewayWebSearch)) {
|
||||
return `${claudeToolPrefix}${name}`;
|
||||
}
|
||||
if (escapeBuiltinToolNames) return `${claudeToolPrefix}${name}`;
|
||||
return isOAuthToken ? applyClaudeToolPrefix(name) : name;
|
||||
}
|
||||
|
||||
function decodeAnthropicToolName(name: string, isOAuthToken: boolean, escapeBuiltinToolNames: boolean): string {
|
||||
if (isOAuthToken) return stripClaudeToolPrefix(name);
|
||||
if (!escapeBuiltinToolNames) return name;
|
||||
const stripped = stripClaudeToolPrefix(name);
|
||||
return ANTHROPIC_BUILTIN_TOOL_NAMES.has(stripped.toLowerCase()) ? stripped : name;
|
||||
if (isOAuthToken || escapeBuiltinToolNames) return stripClaudeToolPrefix(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
const ANTHROPIC_MANY_IMAGE_THRESHOLD = 20;
|
||||
|
||||
@@ -823,7 +823,7 @@ describe("Anthropic request fingerprint alignment", () => {
|
||||
expect(payload.metadata?.user_id).not.toBe("invalid-user-id");
|
||||
expectClaudeMetadataUserId(payload.metadata?.user_id);
|
||||
});
|
||||
it("escapes Anthropic built-in tool names for compatible gateways", async () => {
|
||||
it("escapes Umans client tool names for unambiguous round-trips", async () => {
|
||||
const tools: Tool[] = [
|
||||
{
|
||||
name: "web_search",
|
||||
@@ -843,6 +843,15 @@ describe("Anthropic request fingerprint alignment", () => {
|
||||
required: ["path"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "_web_search",
|
||||
description: "literal prefixed search",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: { query: { type: "string" } },
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const payload = (await captureAnthropicPayload(
|
||||
@@ -858,7 +867,7 @@ describe("Anthropic request fingerprint alignment", () => {
|
||||
tool_choice?: { type?: string; name?: string };
|
||||
};
|
||||
|
||||
expect(payload.tools?.map(tool => tool.name)).toEqual(["_web_search", "read"]);
|
||||
expect(payload.tools?.map(tool => tool.name)).toEqual(["_web_search", "_read", "__web_search"]);
|
||||
expect(payload.tool_choice).toEqual({ type: "tool", name: "_web_search" });
|
||||
});
|
||||
|
||||
@@ -901,7 +910,7 @@ describe("Anthropic request fingerprint alignment", () => {
|
||||
tool_choice?: { type?: string; name?: string };
|
||||
};
|
||||
|
||||
expect(payload.tools?.map(tool => tool.name)).toEqual(["web_search", "read"]);
|
||||
expect(payload.tools?.map(tool => tool.name)).toEqual(["web_search", "_read"]);
|
||||
expect(payload.tool_choice).toEqual({ type: "tool", name: "web_search" });
|
||||
});
|
||||
|
||||
|
||||
@@ -383,6 +383,66 @@ describe("anthropic stream envelope handling", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("decodes escaped literal-prefixed Umans tool names", async () => {
|
||||
vi.spyOn(AnthropicMessages.prototype, "create").mockImplementation(
|
||||
() =>
|
||||
createMockRequest([
|
||||
{
|
||||
type: "message_start",
|
||||
message: {
|
||||
id: "msg_literal_tool",
|
||||
usage: {
|
||||
input_tokens: 12,
|
||||
output_tokens: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "content_block_start",
|
||||
index: 0,
|
||||
content_block: { type: "tool_use", id: "tool_1", name: "__web_search", input: {} },
|
||||
},
|
||||
{
|
||||
type: "content_block_delta",
|
||||
index: 0,
|
||||
delta: { type: "input_json_delta", partial_json: '{"query":"literal"}' },
|
||||
},
|
||||
{ type: "content_block_stop", index: 0 },
|
||||
{
|
||||
type: "message_delta",
|
||||
delta: { stop_reason: "tool_use" },
|
||||
usage: {
|
||||
input_tokens: 12,
|
||||
output_tokens: 4,
|
||||
cache_read_input_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
},
|
||||
},
|
||||
{ type: "message_stop" },
|
||||
]) as never,
|
||||
);
|
||||
|
||||
const stream = streamAnthropic(umansModel, context, { apiKey: "sk-ant-test" });
|
||||
const events: AssistantMessageEvent[] = [];
|
||||
for await (const event of stream) {
|
||||
events.push(event);
|
||||
}
|
||||
const result = await stream.result();
|
||||
|
||||
expect(countEvents(events, "toolcall_start")).toBe(1);
|
||||
expect(result.stopReason).toBe("toolUse");
|
||||
expect(result.content).toEqual([
|
||||
{
|
||||
type: "toolCall",
|
||||
id: "tool_1",
|
||||
name: "_web_search",
|
||||
arguments: { query: "literal" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("ignores Umans gateway web search server blocks and keeps final text", async () => {
|
||||
vi.spyOn(AnthropicMessages.prototype, "create").mockImplementation(
|
||||
() =>
|
||||
|
||||
Reference in New Issue
Block a user