Skip to content
Closed
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
89 changes: 89 additions & 0 deletions packages/opencode/test/provider/copilot/finish-reason.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { describe, test, expect } from "bun:test"
import { mapOpenAICompatibleFinishReason } from "../../../src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason"
import { mapOpenAIResponseFinishReason } from "../../../src/provider/sdk/copilot/responses/map-openai-responses-finish-reason"

// ---------------------------------------------------------------------------
// mapOpenAICompatibleFinishReason (Chat API path)
// ---------------------------------------------------------------------------

describe("mapOpenAICompatibleFinishReason", () => {
test("maps 'stop' to 'stop'", () => {
expect(mapOpenAICompatibleFinishReason("stop")).toBe("stop")
})

test("maps 'length' to 'length'", () => {
expect(mapOpenAICompatibleFinishReason("length")).toBe("length")
})

test("maps 'content_filter' to 'content-filter'", () => {
expect(mapOpenAICompatibleFinishReason("content_filter")).toBe("content-filter")
})

test("maps 'function_call' to 'tool-calls'", () => {
expect(mapOpenAICompatibleFinishReason("function_call")).toBe("tool-calls")
})

test("maps 'tool_calls' to 'tool-calls'", () => {
expect(mapOpenAICompatibleFinishReason("tool_calls")).toBe("tool-calls")
})

test("maps null to 'unknown'", () => {
expect(mapOpenAICompatibleFinishReason(null)).toBe("unknown")
})

test("maps undefined to 'unknown'", () => {
expect(mapOpenAICompatibleFinishReason(undefined)).toBe("unknown")
})

test("maps empty string to 'unknown'", () => {
expect(mapOpenAICompatibleFinishReason("")).toBe("unknown")
})

test("maps unrecognized string to 'unknown'", () => {
expect(mapOpenAICompatibleFinishReason("something_else")).toBe("unknown")
})
})

// ---------------------------------------------------------------------------
// mapOpenAIResponseFinishReason (Responses API path)
// ---------------------------------------------------------------------------

describe("mapOpenAIResponseFinishReason", () => {
test("null without function call returns 'stop'", () => {
expect(mapOpenAIResponseFinishReason({ finishReason: null, hasFunctionCall: false })).toBe("stop")
})

test("null with function call returns 'tool-calls'", () => {
expect(mapOpenAIResponseFinishReason({ finishReason: null, hasFunctionCall: true })).toBe("tool-calls")
})

test("undefined without function call returns 'stop'", () => {
expect(mapOpenAIResponseFinishReason({ finishReason: undefined, hasFunctionCall: false })).toBe("stop")
})

test("undefined with function call returns 'tool-calls'", () => {
expect(mapOpenAIResponseFinishReason({ finishReason: undefined, hasFunctionCall: true })).toBe("tool-calls")
})

test("'max_output_tokens' maps to 'length'", () => {
expect(mapOpenAIResponseFinishReason({ finishReason: "max_output_tokens", hasFunctionCall: false })).toBe("length")
})

test("'max_output_tokens' maps to 'length' even with function call", () => {
expect(mapOpenAIResponseFinishReason({ finishReason: "max_output_tokens", hasFunctionCall: true })).toBe("length")
})

test("'content_filter' maps to 'content-filter'", () => {
expect(mapOpenAIResponseFinishReason({ finishReason: "content_filter", hasFunctionCall: false })).toBe(
"content-filter",
)
})

test("unknown string without function call returns 'unknown'", () => {
expect(mapOpenAIResponseFinishReason({ finishReason: "something_else", hasFunctionCall: false })).toBe("unknown")
})

test("unknown string with function call returns 'tool-calls'", () => {
expect(mapOpenAIResponseFinishReason({ finishReason: "something_else", hasFunctionCall: true })).toBe("tool-calls")
})
})
114 changes: 114 additions & 0 deletions packages/opencode/test/provider/copilot/prepare-tools.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { describe, test, expect } from "bun:test"
import { prepareTools } from "../../../src/provider/sdk/copilot/chat/openai-compatible-prepare-tools"

describe("prepareTools", () => {
test("undefined tools returns all undefined", () => {
const result = prepareTools({ tools: undefined })
expect(result.tools).toBeUndefined()
expect(result.toolChoice).toBeUndefined()
expect(result.toolWarnings).toEqual([])
})

test("empty tools array returns all undefined", () => {
const result = prepareTools({ tools: [] })
expect(result.tools).toBeUndefined()
expect(result.toolChoice).toBeUndefined()
expect(result.toolWarnings).toEqual([])
})

test("converts a single function tool to OpenAI format", () => {
const result = prepareTools({
tools: [
{
type: "function",
name: "get_weather",
description: "Get weather for a city",
inputSchema: { type: "object", properties: { city: { type: "string" } } },
},
],
})
expect(result.tools).toEqual([
{
type: "function",
function: {
name: "get_weather",
description: "Get weather for a city",
parameters: { type: "object", properties: { city: { type: "string" } } },
},
},
])
expect(result.toolWarnings).toEqual([])
})

test("provider-defined tool emits unsupported-tool warning", () => {
const providerTool = {
type: "provider-defined" as const,
id: "some-provider-tool",
name: "provider_tool",
args: {},
}
const result = prepareTools({
tools: [providerTool],
})
expect(result.toolWarnings).toHaveLength(1)
expect(result.toolWarnings[0]).toEqual({
type: "unsupported-tool",
tool: providerTool,
})
// Provider-defined tools are not included in the output tools array
expect(result.tools).toEqual([])
})

test("toolChoice 'auto' passes through", () => {
const result = prepareTools({
tools: [
{ type: "function", name: "foo", description: "test", inputSchema: {} },
],
toolChoice: { type: "auto" },
})
expect(result.toolChoice).toBe("auto")
})

test("toolChoice 'none' passes through", () => {
const result = prepareTools({
tools: [
{ type: "function", name: "foo", description: "test", inputSchema: {} },
],
toolChoice: { type: "none" },
})
expect(result.toolChoice).toBe("none")
})

test("toolChoice 'required' passes through", () => {
const result = prepareTools({
tools: [
{ type: "function", name: "foo", description: "test", inputSchema: {} },
],
toolChoice: { type: "required" },
})
expect(result.toolChoice).toBe("required")
})

test("toolChoice type 'tool' converts to function format", () => {
const result = prepareTools({
tools: [
{ type: "function", name: "my_func", description: "desc", inputSchema: {} },
],
toolChoice: { type: "tool", toolName: "my_func" },
})
expect(result.toolChoice).toEqual({
type: "function",
function: { name: "my_func" },
})
})

test("no toolChoice returns undefined toolChoice", () => {
const result = prepareTools({
tools: [
{ type: "function", name: "foo", description: "test", inputSchema: {} },
],
})
expect(result.toolChoice).toBeUndefined()
expect(result.tools).toHaveLength(1)
})
})
Loading