|
@@ -15,6 +15,7 @@ vitest.mock("../fetchers/modelCache", () => ({
|
|
|
contextWindow: 200000,
|
|
contextWindow: 200000,
|
|
|
supportsImages: true,
|
|
supportsImages: true,
|
|
|
supportsPromptCache: true,
|
|
supportsPromptCache: true,
|
|
|
|
|
+ supportsNativeTools: true,
|
|
|
inputPrice: 3,
|
|
inputPrice: 3,
|
|
|
outputPrice: 15,
|
|
outputPrice: 15,
|
|
|
cacheWritesPrice: 3.75,
|
|
cacheWritesPrice: 3.75,
|
|
@@ -27,6 +28,7 @@ vitest.mock("../fetchers/modelCache", () => ({
|
|
|
contextWindow: 200000,
|
|
contextWindow: 200000,
|
|
|
supportsImages: true,
|
|
supportsImages: true,
|
|
|
supportsPromptCache: true,
|
|
supportsPromptCache: true,
|
|
|
|
|
+ supportsNativeTools: true,
|
|
|
inputPrice: 3,
|
|
inputPrice: 3,
|
|
|
outputPrice: 15,
|
|
outputPrice: 15,
|
|
|
cacheWritesPrice: 3.75,
|
|
cacheWritesPrice: 3.75,
|
|
@@ -39,6 +41,7 @@ vitest.mock("../fetchers/modelCache", () => ({
|
|
|
contextWindow: 200000,
|
|
contextWindow: 200000,
|
|
|
supportsImages: true,
|
|
supportsImages: true,
|
|
|
supportsPromptCache: true,
|
|
supportsPromptCache: true,
|
|
|
|
|
+ supportsNativeTools: true,
|
|
|
inputPrice: 3,
|
|
inputPrice: 3,
|
|
|
outputPrice: 15,
|
|
outputPrice: 15,
|
|
|
cacheWritesPrice: 3.75,
|
|
cacheWritesPrice: 3.75,
|
|
@@ -51,6 +54,7 @@ vitest.mock("../fetchers/modelCache", () => ({
|
|
|
contextWindow: 128000,
|
|
contextWindow: 128000,
|
|
|
supportsImages: true,
|
|
supportsImages: true,
|
|
|
supportsPromptCache: false,
|
|
supportsPromptCache: false,
|
|
|
|
|
+ supportsNativeTools: true,
|
|
|
inputPrice: 5,
|
|
inputPrice: 5,
|
|
|
outputPrice: 15,
|
|
outputPrice: 15,
|
|
|
description: "GPT-4o",
|
|
description: "GPT-4o",
|
|
@@ -60,6 +64,7 @@ vitest.mock("../fetchers/modelCache", () => ({
|
|
|
contextWindow: 128000,
|
|
contextWindow: 128000,
|
|
|
supportsImages: true,
|
|
supportsImages: true,
|
|
|
supportsPromptCache: false,
|
|
supportsPromptCache: false,
|
|
|
|
|
+ supportsNativeTools: true,
|
|
|
inputPrice: 1,
|
|
inputPrice: 1,
|
|
|
outputPrice: 3,
|
|
outputPrice: 3,
|
|
|
description: "O3 Mini",
|
|
description: "O3 Mini",
|
|
@@ -328,4 +333,225 @@ describe("UnboundHandler", () => {
|
|
|
expect(modelInfo.info).toBeDefined()
|
|
expect(modelInfo.info).toBeDefined()
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
|
|
+ describe("Native Tool Calling", () => {
|
|
|
|
|
+ const testTools = [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "function" as const,
|
|
|
|
|
+ function: {
|
|
|
|
|
+ name: "test_tool",
|
|
|
|
|
+ description: "A test tool",
|
|
|
|
|
+ parameters: {
|
|
|
|
|
+ type: "object",
|
|
|
|
|
+ properties: {
|
|
|
|
|
+ arg1: { type: "string", description: "First argument" },
|
|
|
|
|
+ },
|
|
|
|
|
+ required: ["arg1"],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ it("should include tools in request when model supports native tools and tools are provided", async () => {
|
|
|
|
|
+ mockWithResponse.mockResolvedValueOnce({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ [Symbol.asyncIterator]: () => ({
|
|
|
|
|
+ async next() {
|
|
|
|
|
+ return { done: true }
|
|
|
|
|
+ },
|
|
|
|
|
+ }),
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const messageGenerator = handler.createMessage("test prompt", [], {
|
|
|
|
|
+ taskId: "test-task-id",
|
|
|
|
|
+ tools: testTools,
|
|
|
|
|
+ toolProtocol: "native",
|
|
|
|
|
+ })
|
|
|
|
|
+ await messageGenerator.next()
|
|
|
|
|
+
|
|
|
|
|
+ expect(mockCreate).toHaveBeenCalledWith(
|
|
|
|
|
+ expect.objectContaining({
|
|
|
|
|
+ tools: expect.arrayContaining([
|
|
|
|
|
+ expect.objectContaining({
|
|
|
|
|
+ type: "function",
|
|
|
|
|
+ function: expect.objectContaining({
|
|
|
|
|
+ name: "test_tool",
|
|
|
|
|
+ }),
|
|
|
|
|
+ }),
|
|
|
|
|
+ ]),
|
|
|
|
|
+ parallel_tool_calls: false,
|
|
|
|
|
+ }),
|
|
|
|
|
+ expect.objectContaining({
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ "X-Unbound-Metadata": expect.stringContaining("roo-code"),
|
|
|
|
|
+ },
|
|
|
|
|
+ }),
|
|
|
|
|
+ )
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should include tool_choice when provided", async () => {
|
|
|
|
|
+ mockWithResponse.mockResolvedValueOnce({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ [Symbol.asyncIterator]: () => ({
|
|
|
|
|
+ async next() {
|
|
|
|
|
+ return { done: true }
|
|
|
|
|
+ },
|
|
|
|
|
+ }),
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const messageGenerator = handler.createMessage("test prompt", [], {
|
|
|
|
|
+ taskId: "test-task-id",
|
|
|
|
|
+ tools: testTools,
|
|
|
|
|
+ toolProtocol: "native",
|
|
|
|
|
+ tool_choice: "auto",
|
|
|
|
|
+ })
|
|
|
|
|
+ await messageGenerator.next()
|
|
|
|
|
+
|
|
|
|
|
+ expect(mockCreate).toHaveBeenCalledWith(
|
|
|
|
|
+ expect.objectContaining({
|
|
|
|
|
+ tool_choice: "auto",
|
|
|
|
|
+ }),
|
|
|
|
|
+ expect.objectContaining({
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ "X-Unbound-Metadata": expect.stringContaining("roo-code"),
|
|
|
|
|
+ },
|
|
|
|
|
+ }),
|
|
|
|
|
+ )
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should not include tools when toolProtocol is xml", async () => {
|
|
|
|
|
+ mockWithResponse.mockResolvedValueOnce({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ [Symbol.asyncIterator]: () => ({
|
|
|
|
|
+ async next() {
|
|
|
|
|
+ return { done: true }
|
|
|
|
|
+ },
|
|
|
|
|
+ }),
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const messageGenerator = handler.createMessage("test prompt", [], {
|
|
|
|
|
+ taskId: "test-task-id",
|
|
|
|
|
+ tools: testTools,
|
|
|
|
|
+ toolProtocol: "xml",
|
|
|
|
|
+ })
|
|
|
|
|
+ await messageGenerator.next()
|
|
|
|
|
+
|
|
|
|
|
+ const callArgs = mockCreate.mock.calls[mockCreate.mock.calls.length - 1][0]
|
|
|
|
|
+ expect(callArgs).not.toHaveProperty("tools")
|
|
|
|
|
+ expect(callArgs).not.toHaveProperty("tool_choice")
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should yield tool_call_partial chunks during streaming", async () => {
|
|
|
|
|
+ mockWithResponse.mockResolvedValueOnce({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ [Symbol.asyncIterator]: () => ({
|
|
|
|
|
+ next: vi
|
|
|
|
|
+ .fn()
|
|
|
|
|
+ .mockResolvedValueOnce({
|
|
|
|
|
+ done: false,
|
|
|
|
|
+ value: {
|
|
|
|
|
+ choices: [
|
|
|
|
|
+ {
|
|
|
|
|
+ delta: {
|
|
|
|
|
+ tool_calls: [
|
|
|
|
|
+ {
|
|
|
|
|
+ index: 0,
|
|
|
|
|
+ id: "call_123",
|
|
|
|
|
+ function: {
|
|
|
|
|
+ name: "test_tool",
|
|
|
|
|
+ arguments: '{"arg1":',
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ .mockResolvedValueOnce({
|
|
|
|
|
+ done: false,
|
|
|
|
|
+ value: {
|
|
|
|
|
+ choices: [
|
|
|
|
|
+ {
|
|
|
|
|
+ delta: {
|
|
|
|
|
+ tool_calls: [
|
|
|
|
|
+ {
|
|
|
|
|
+ index: 0,
|
|
|
|
|
+ function: {
|
|
|
|
|
+ arguments: '"value"}',
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ .mockResolvedValueOnce({ done: true }),
|
|
|
|
|
+ }),
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const stream = handler.createMessage("test prompt", [], {
|
|
|
|
|
+ taskId: "test-task-id",
|
|
|
|
|
+ tools: testTools,
|
|
|
|
|
+ toolProtocol: "native",
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const chunks = []
|
|
|
|
|
+ for await (const chunk of stream) {
|
|
|
|
|
+ chunks.push(chunk)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ expect(chunks).toContainEqual({
|
|
|
|
|
+ type: "tool_call_partial",
|
|
|
|
|
+ index: 0,
|
|
|
|
|
+ id: "call_123",
|
|
|
|
|
+ name: "test_tool",
|
|
|
|
|
+ arguments: '{"arg1":',
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ expect(chunks).toContainEqual({
|
|
|
|
|
+ type: "tool_call_partial",
|
|
|
|
|
+ index: 0,
|
|
|
|
|
+ id: undefined,
|
|
|
|
|
+ name: undefined,
|
|
|
|
|
+ arguments: '"value"}',
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should set parallel_tool_calls based on metadata", async () => {
|
|
|
|
|
+ mockWithResponse.mockResolvedValueOnce({
|
|
|
|
|
+ data: {
|
|
|
|
|
+ [Symbol.asyncIterator]: () => ({
|
|
|
|
|
+ async next() {
|
|
|
|
|
+ return { done: true }
|
|
|
|
|
+ },
|
|
|
|
|
+ }),
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const messageGenerator = handler.createMessage("test prompt", [], {
|
|
|
|
|
+ taskId: "test-task-id",
|
|
|
|
|
+ tools: testTools,
|
|
|
|
|
+ toolProtocol: "native",
|
|
|
|
|
+ parallelToolCalls: true,
|
|
|
|
|
+ })
|
|
|
|
|
+ await messageGenerator.next()
|
|
|
|
|
+
|
|
|
|
|
+ expect(mockCreate).toHaveBeenCalledWith(
|
|
|
|
|
+ expect.objectContaining({
|
|
|
|
|
+ parallel_tool_calls: true,
|
|
|
|
|
+ }),
|
|
|
|
|
+ expect.objectContaining({
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ "X-Unbound-Metadata": expect.stringContaining("roo-code"),
|
|
|
|
|
+ },
|
|
|
|
|
+ }),
|
|
|
|
|
+ )
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|