Skip to content

Commit abb3c40

Browse files
committed
Merge remote-tracking branch 'origin/test/hourly-20260327-1918' into test/consolidate-18-test-prs
2 parents 4610f26 + 9c5b445 commit abb3c40

2 files changed

Lines changed: 203 additions & 0 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { describe, test, expect } from "bun:test"
2+
import { mapOpenAICompatibleFinishReason } from "../../../src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason"
3+
import { mapOpenAIResponseFinishReason } from "../../../src/provider/sdk/copilot/responses/map-openai-responses-finish-reason"
4+
5+
// ---------------------------------------------------------------------------
6+
// mapOpenAICompatibleFinishReason (Chat API path)
7+
// ---------------------------------------------------------------------------
8+
9+
describe("mapOpenAICompatibleFinishReason", () => {
10+
test("maps 'stop' to 'stop'", () => {
11+
expect(mapOpenAICompatibleFinishReason("stop")).toBe("stop")
12+
})
13+
14+
test("maps 'length' to 'length'", () => {
15+
expect(mapOpenAICompatibleFinishReason("length")).toBe("length")
16+
})
17+
18+
test("maps 'content_filter' to 'content-filter'", () => {
19+
expect(mapOpenAICompatibleFinishReason("content_filter")).toBe("content-filter")
20+
})
21+
22+
test("maps 'function_call' to 'tool-calls'", () => {
23+
expect(mapOpenAICompatibleFinishReason("function_call")).toBe("tool-calls")
24+
})
25+
26+
test("maps 'tool_calls' to 'tool-calls'", () => {
27+
expect(mapOpenAICompatibleFinishReason("tool_calls")).toBe("tool-calls")
28+
})
29+
30+
test("maps null to 'unknown'", () => {
31+
expect(mapOpenAICompatibleFinishReason(null)).toBe("unknown")
32+
})
33+
34+
test("maps undefined to 'unknown'", () => {
35+
expect(mapOpenAICompatibleFinishReason(undefined)).toBe("unknown")
36+
})
37+
38+
test("maps empty string to 'unknown'", () => {
39+
expect(mapOpenAICompatibleFinishReason("")).toBe("unknown")
40+
})
41+
42+
test("maps unrecognized string to 'unknown'", () => {
43+
expect(mapOpenAICompatibleFinishReason("something_else")).toBe("unknown")
44+
})
45+
})
46+
47+
// ---------------------------------------------------------------------------
48+
// mapOpenAIResponseFinishReason (Responses API path)
49+
// ---------------------------------------------------------------------------
50+
51+
describe("mapOpenAIResponseFinishReason", () => {
52+
test("null without function call returns 'stop'", () => {
53+
expect(mapOpenAIResponseFinishReason({ finishReason: null, hasFunctionCall: false })).toBe("stop")
54+
})
55+
56+
test("null with function call returns 'tool-calls'", () => {
57+
expect(mapOpenAIResponseFinishReason({ finishReason: null, hasFunctionCall: true })).toBe("tool-calls")
58+
})
59+
60+
test("undefined without function call returns 'stop'", () => {
61+
expect(mapOpenAIResponseFinishReason({ finishReason: undefined, hasFunctionCall: false })).toBe("stop")
62+
})
63+
64+
test("undefined with function call returns 'tool-calls'", () => {
65+
expect(mapOpenAIResponseFinishReason({ finishReason: undefined, hasFunctionCall: true })).toBe("tool-calls")
66+
})
67+
68+
test("'max_output_tokens' maps to 'length'", () => {
69+
expect(mapOpenAIResponseFinishReason({ finishReason: "max_output_tokens", hasFunctionCall: false })).toBe("length")
70+
})
71+
72+
test("'max_output_tokens' maps to 'length' even with function call", () => {
73+
expect(mapOpenAIResponseFinishReason({ finishReason: "max_output_tokens", hasFunctionCall: true })).toBe("length")
74+
})
75+
76+
test("'content_filter' maps to 'content-filter'", () => {
77+
expect(mapOpenAIResponseFinishReason({ finishReason: "content_filter", hasFunctionCall: false })).toBe(
78+
"content-filter",
79+
)
80+
})
81+
82+
test("unknown string without function call returns 'unknown'", () => {
83+
expect(mapOpenAIResponseFinishReason({ finishReason: "something_else", hasFunctionCall: false })).toBe("unknown")
84+
})
85+
86+
test("unknown string with function call returns 'tool-calls'", () => {
87+
expect(mapOpenAIResponseFinishReason({ finishReason: "something_else", hasFunctionCall: true })).toBe("tool-calls")
88+
})
89+
})
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { describe, test, expect } from "bun:test"
2+
import { prepareTools } from "../../../src/provider/sdk/copilot/chat/openai-compatible-prepare-tools"
3+
4+
describe("prepareTools", () => {
5+
test("undefined tools returns all undefined", () => {
6+
const result = prepareTools({ tools: undefined })
7+
expect(result.tools).toBeUndefined()
8+
expect(result.toolChoice).toBeUndefined()
9+
expect(result.toolWarnings).toEqual([])
10+
})
11+
12+
test("empty tools array returns all undefined", () => {
13+
const result = prepareTools({ tools: [] })
14+
expect(result.tools).toBeUndefined()
15+
expect(result.toolChoice).toBeUndefined()
16+
expect(result.toolWarnings).toEqual([])
17+
})
18+
19+
test("converts a single function tool to OpenAI format", () => {
20+
const result = prepareTools({
21+
tools: [
22+
{
23+
type: "function",
24+
name: "get_weather",
25+
description: "Get weather for a city",
26+
inputSchema: { type: "object", properties: { city: { type: "string" } } },
27+
},
28+
],
29+
})
30+
expect(result.tools).toEqual([
31+
{
32+
type: "function",
33+
function: {
34+
name: "get_weather",
35+
description: "Get weather for a city",
36+
parameters: { type: "object", properties: { city: { type: "string" } } },
37+
},
38+
},
39+
])
40+
expect(result.toolWarnings).toEqual([])
41+
})
42+
43+
test("provider-defined tool emits unsupported-tool warning", () => {
44+
const providerTool = {
45+
type: "provider-defined" as const,
46+
id: "some-provider-tool",
47+
name: "provider_tool",
48+
args: {},
49+
}
50+
const result = prepareTools({
51+
tools: [providerTool],
52+
})
53+
expect(result.toolWarnings).toHaveLength(1)
54+
expect(result.toolWarnings[0]).toEqual({
55+
type: "unsupported-tool",
56+
tool: providerTool,
57+
})
58+
// Provider-defined tools are not included in the output tools array
59+
expect(result.tools).toEqual([])
60+
})
61+
62+
test("toolChoice 'auto' passes through", () => {
63+
const result = prepareTools({
64+
tools: [
65+
{ type: "function", name: "foo", description: "test", inputSchema: {} },
66+
],
67+
toolChoice: { type: "auto" },
68+
})
69+
expect(result.toolChoice).toBe("auto")
70+
})
71+
72+
test("toolChoice 'none' passes through", () => {
73+
const result = prepareTools({
74+
tools: [
75+
{ type: "function", name: "foo", description: "test", inputSchema: {} },
76+
],
77+
toolChoice: { type: "none" },
78+
})
79+
expect(result.toolChoice).toBe("none")
80+
})
81+
82+
test("toolChoice 'required' passes through", () => {
83+
const result = prepareTools({
84+
tools: [
85+
{ type: "function", name: "foo", description: "test", inputSchema: {} },
86+
],
87+
toolChoice: { type: "required" },
88+
})
89+
expect(result.toolChoice).toBe("required")
90+
})
91+
92+
test("toolChoice type 'tool' converts to function format", () => {
93+
const result = prepareTools({
94+
tools: [
95+
{ type: "function", name: "my_func", description: "desc", inputSchema: {} },
96+
],
97+
toolChoice: { type: "tool", toolName: "my_func" },
98+
})
99+
expect(result.toolChoice).toEqual({
100+
type: "function",
101+
function: { name: "my_func" },
102+
})
103+
})
104+
105+
test("no toolChoice returns undefined toolChoice", () => {
106+
const result = prepareTools({
107+
tools: [
108+
{ type: "function", name: "foo", description: "test", inputSchema: {} },
109+
],
110+
})
111+
expect(result.toolChoice).toBeUndefined()
112+
expect(result.tools).toHaveLength(1)
113+
})
114+
})

0 commit comments

Comments
 (0)