From 0acb1ce6880b9327ab59e6d6568c9dccd7ad3d74 Mon Sep 17 00:00:00 2001 From: Alex McGraw Date: Mon, 4 May 2026 13:30:41 -0500 Subject: [PATCH 1/3] tool calling with ask sage as provider --- core/llm/llms/Asksage.ts | 6 + core/llm/toolSupport.ts | 4 + docs/package-lock.json | 11 - extensions/vscode/package-lock.json | 44 +- gui/package-lock.json | 39 +- manual-testing-sandbox/test.py | 10 + packages/openai-adapters/package-lock.json | 409 ++---------------- packages/openai-adapters/src/apis/AskSage.ts | 34 +- .../src/test/asksage-adapter.vitest.ts | 100 +++++ 9 files changed, 195 insertions(+), 462 deletions(-) diff --git a/core/llm/llms/Asksage.ts b/core/llm/llms/Asksage.ts index b2248473f04..fdcaed4b228 100644 --- a/core/llm/llms/Asksage.ts +++ b/core/llm/llms/Asksage.ts @@ -8,6 +8,7 @@ import { ToolCallDelta, } from "../../index.js"; import { BaseLLM } from "../index.js"; +import { LlmApiRequestType } from "../openaiTypeConverters.js"; import { AskSageTool, AskSageToolChoice, @@ -63,6 +64,11 @@ class Asksage extends BaseLLM { model: "gpt-4o", }; + protected useOpenAIAdapterFor: (LlmApiRequestType | "*")[] = [ + "chat", + "streamChat", + ]; + private sessionTokenPromise: Promise | null = null; private tokenTimestamp: number = 0; private email?: string; diff --git a/core/llm/toolSupport.ts b/core/llm/toolSupport.ts index 88e92eeb7d2..b13334a4042 100644 --- a/core/llm/toolSupport.ts +++ b/core/llm/toolSupport.ts @@ -506,6 +506,10 @@ export const PROVIDER_TOOL_SUPPORT: Record boolean> = return false; }, + askSage: (_model) => { + // Ask Sage proxies to tool-capable models (GPT-4o, Claude, Gemini, etc.) + return true; + }, }; export function isRecommendedAgentModel(modelName: string): boolean { diff --git a/docs/package-lock.json b/docs/package-lock.json index e5e5a03c3e3..1512735dc89 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -4788,17 +4788,6 @@ "@noble/hashes": "^1.1.5" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", diff --git a/extensions/vscode/package-lock.json b/extensions/vscode/package-lock.json index 32f15d25f8a..ddf5a365415 100644 --- a/extensions/vscode/package-lock.json +++ b/extensions/vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "1.3.35", + "version": "1.3.39", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "continue", - "version": "1.3.35", + "version": "1.3.39", "license": "Apache-2.0", "dependencies": { "@continuedev/config-types": "file:../../packages/config-types", @@ -7659,18 +7659,6 @@ } } }, - "node_modules/inquirer/node_modules/@types/node": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.4.0.tgz", - "integrity": "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "undici-types": "~7.18.0" - } - }, "node_modules/inquirer/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -7694,15 +7682,6 @@ "node": ">=8" } }, - "node_modules/inquirer/node_modules/undici-types": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.1.tgz", - "integrity": "sha512-DjWG0xEjLMDnfcoZzX9A1d1IgDkH+aFh2UQmjKAo44W5Mz+HaUfNNpIqBL6CnXlYkoZvuPgoORE2H5TzSNJ8MA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -14221,17 +14200,6 @@ "node": ">=18" } }, - "node_modules/vite-node/node_modules/@types/node": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.4.0.tgz", - "integrity": "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "undici-types": "~7.18.0" - } - }, "node_modules/vite-node/node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -14303,14 +14271,6 @@ } } }, - "node_modules/vite-node/node_modules/undici-types": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.1.tgz", - "integrity": "sha512-DjWG0xEjLMDnfcoZzX9A1d1IgDkH+aFh2UQmjKAo44W5Mz+HaUfNNpIqBL6CnXlYkoZvuPgoORE2H5TzSNJ8MA==", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/vite-node/node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", diff --git a/gui/package-lock.json b/gui/package-lock.json index d96ea3b2577..3b700eb6f02 100644 --- a/gui/package-lock.json +++ b/gui/package-lock.json @@ -176,13 +176,12 @@ "puppeteer-chromium-resolver": "^23.0.0", "quick-lru": "^7.0.0", "replicate": "^0.26.0", - "request": "^2.88.2", "shell-quote": "^1.8.3", "socket.io-client": "^4.7.3", "sqlite": "^5.1.1", "sqlite3": "^5.1.7", "system-ca": "^1.0.3", - "tar": "^7.5.10", + "tar": "^7.5.13", "tree-sitter-wasms": "^0.1.11", "untildify": "^6.0.0", "uuid": "^9.0.1", @@ -221,7 +220,7 @@ "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", "cross-env": "^7.0.3", - "esbuild": "0.17.19", + "esbuild": "^0.25.0", "eslint": "^8", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", @@ -6347,24 +6346,6 @@ "chevrotain": "^11.0.0" } }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", @@ -13472,22 +13453,6 @@ "pify": "^2.3.0" } }, - "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", diff --git a/manual-testing-sandbox/test.py b/manual-testing-sandbox/test.py index df0f203cc50..4ea3104d9e3 100644 --- a/manual-testing-sandbox/test.py +++ b/manual-testing-sandbox/test.py @@ -10,6 +10,16 @@ def subtract(self, number): self.result -= number return self + def multiply(self, number): + self.result *= number + return self + + def divide(self, number): + if number == 0: + raise ValueError("Cannot divide by zero") + self.result /= number + return self + def reset(self): self.result = 0 return self diff --git a/packages/openai-adapters/package-lock.json b/packages/openai-adapters/package-lock.json index 38133d23a20..0fbf3667537 100644 --- a/packages/openai-adapters/package-lock.json +++ b/packages/openai-adapters/package-lock.json @@ -1691,43 +1691,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@emnapi/core": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", - "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", - "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", - "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", @@ -2199,6 +2162,7 @@ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -3091,20 +3055,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, "node_modules/@octokit/auth-token": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", @@ -3288,17 +3238,6 @@ "node": ">=8.0.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@pkgr/core": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", @@ -4968,18 +4907,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -5163,294 +5090,6 @@ "license": "ISC", "peer": true }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, "node_modules/@vercel/oidc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz", @@ -5861,7 +5500,8 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/base64-js": { "version": "1.5.1", @@ -5932,6 +5572,7 @@ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -6141,6 +5782,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -6860,7 +6502,8 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", @@ -6898,7 +6541,8 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/emojilib": { "version": "2.4.0", @@ -7493,6 +7137,7 @@ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -7510,6 +7155,7 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">=14" }, @@ -7686,6 +7332,7 @@ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -7935,6 +7582,7 @@ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.19" } @@ -8206,6 +7854,7 @@ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", + "peer": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -10115,6 +9764,7 @@ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "brace-expansion": "^2.0.2" }, @@ -10141,6 +9791,7 @@ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, "license": "BlueOak-1.0.0", + "peer": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -12485,7 +12136,8 @@ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, - "license": "BlueOak-1.0.0" + "license": "BlueOak-1.0.0", + "peer": true }, "node_modules/parent-module": { "version": "1.0.1", @@ -12609,6 +12261,7 @@ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", + "peer": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -12625,7 +12278,8 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/path-type": { "version": "4.0.0", @@ -16658,6 +16312,7 @@ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -16677,6 +16332,7 @@ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -16692,6 +16348,7 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -16701,7 +16358,8 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", @@ -16709,6 +16367,7 @@ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -16722,6 +16381,7 @@ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^6.2.2" }, @@ -16739,6 +16399,7 @@ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -16752,6 +16413,7 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -17800,6 +17462,7 @@ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -17819,6 +17482,7 @@ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -17837,6 +17501,7 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -17846,7 +17511,8 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", @@ -17854,6 +17520,7 @@ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -17869,6 +17536,7 @@ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -17882,6 +17550,7 @@ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17903,6 +17572,7 @@ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" @@ -17917,6 +17587,7 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">=14" }, diff --git a/packages/openai-adapters/src/apis/AskSage.ts b/packages/openai-adapters/src/apis/AskSage.ts index 18c4f1bd438..af0912e61fb 100644 --- a/packages/openai-adapters/src/apis/AskSage.ts +++ b/packages/openai-adapters/src/apis/AskSage.ts @@ -60,9 +60,12 @@ export class AskSageApi implements BaseLlmApi { constructor(private config: AskSageConfig) { this.apiBase = config.apiBase ?? DEFAULT_API_URL; - this.userApiUrl = config.env?.userApiUrl ?? DEFAULT_USER_API_URL; + this.userApiUrl = + config.env?.userApiUrl ?? + process.env.ASKSAGE_USER_API_URL ?? + DEFAULT_USER_API_URL; this.apiKey = config.apiKey; - this.email = config.env?.email; + this.email = config.env?.email ?? process.env.ASKSAGE_EMAIL; this.fetchFn = customFetch(config.requestOptions); } @@ -177,9 +180,34 @@ export class AskSageApi implements BaseLlmApi { .map((p) => (p as { type: "text"; text: string }).text) .join("\n") : ""; - if (content) { + + // Extract tool calls from assistant message (for multi-turn history) + const toolCalls = (msg as any).tool_calls as + | Array<{ + id: string; + function: { name: string; arguments: string }; + }> + | undefined; + + if (content && !toolCalls?.length) { + // Simple text response + messageArray.push({ user: "gpt", message: content }); + } else if (toolCalls?.length) { + // Serialize tool calls so the model sees what was requested + const toolCallText = toolCalls + .map( + (tc) => + `[Tool call ${tc.id}: ${tc.function.name}(${tc.function.arguments})]`, + ) + .join("\n"); + const fullMessage = content + ? `${content}\n${toolCallText}` + : toolCallText; + messageArray.push({ user: "gpt", message: fullMessage }); + } else if (content) { messageArray.push({ user: "gpt", message: content }); } + // If no content and no tool_calls, skip (empty message) } else if (msg.role === "tool") { // Include tool results as user messages const content = diff --git a/packages/openai-adapters/src/test/asksage-adapter.vitest.ts b/packages/openai-adapters/src/test/asksage-adapter.vitest.ts index 1bdd75b1210..d449a5f552e 100644 --- a/packages/openai-adapters/src/test/asksage-adapter.vitest.ts +++ b/packages/openai-adapters/src/test/asksage-adapter.vitest.ts @@ -246,6 +246,106 @@ describe("AskSage Adapter Tests", () => { }); }); + test("chatCompletionNonStream with multi-turn tool calling should preserve tool call messages", async () => { + await runAdapterTest({ + config: { + provider: "askSage", + apiKey: "test-api-key", + apiBase: "https://api.asksage.ai/server", + }, + methodToTest: "chatCompletionNonStream", + params: [ + { + model: "gpt-4o", + messages: [ + { role: "user", content: "What's the weather in NYC?" }, + { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_123", + type: "function", + function: { + name: "get_weather", + arguments: '{"location":"NYC"}', + }, + }, + ], + }, + { + role: "tool", + tool_call_id: "call_123", + content: '{"temp": 72, "condition": "sunny"}', + }, + ], + tools: [ + { + type: "function", + function: { + name: "get_weather", + description: "Get current weather", + parameters: { + type: "object", + properties: { + location: { type: "string" }, + }, + }, + }, + }, + ], + }, + new AbortController().signal, + ], + expectedRequest: { + url: "https://api.asksage.ai/server/query", + method: "POST", + headers: { + "Content-Type": "application/json", + accept: "application/json", + "x-access-tokens": "test-api-key", + }, + body: { + message: [ + { user: "me", message: "What's the weather in NYC?" }, + { + user: "gpt", + message: '[Tool call call_123: get_weather({"location":"NYC"})]', + }, + { + user: "me", + message: + 'Tool result for call_123:\n{"temp": 72, "condition": "sunny"}', + }, + ], + model: "gpt-4o", + temperature: 0, + mode: "chat", + limit_references: 0, + tools: [ + { + type: "function", + function: { + name: "get_weather", + description: "Get current weather", + parameters: { + type: "object", + properties: { + location: { type: "string" }, + }, + }, + }, + }, + ], + }, + }, + mockResponse: { + text: "It's currently 72°F and sunny in NYC.", + status: 200, + }, + }); + }); + test("chatCompletionStream should send a valid request", async () => { await runAdapterTest({ config: { From 14094549b92b00f7ef9cec6afd2e656a91c8be09 Mon Sep 17 00:00:00 2001 From: Alex McGraw Date: Mon, 4 May 2026 15:34:22 -0500 Subject: [PATCH 2/3] fix tool calling for anthropic and google models --- core/llm/llms/Asksage.ts | 51 ++++++--- packages/openai-adapters/src/apis/AskSage.ts | 101 +++++++++++++----- .../src/test/asksage-adapter.vitest.ts | 84 +++++++++++++++ packages/openai-adapters/src/types.ts | 37 ++++++- 4 files changed, 231 insertions(+), 42 deletions(-) diff --git a/core/llm/llms/Asksage.ts b/core/llm/llms/Asksage.ts index fdcaed4b228..5020bb08cac 100644 --- a/core/llm/llms/Asksage.ts +++ b/core/llm/llms/Asksage.ts @@ -389,21 +389,44 @@ class Asksage extends BaseLLM { const data = (await response.json()) as AskSageResponse; - // Extract tool calls from response (check both top-level and choices format) + // Extract tool calls from response, preferring the unified format const rawToolCalls = - data.tool_calls || data.choices?.[0]?.message?.tool_calls; - - // Convert to ToolCallDelta format if present - const toolCalls: ToolCallDelta[] | undefined = rawToolCalls?.map( - (tc) => ({ - id: tc.id, - type: tc.type, - function: { - name: tc.function.name, - arguments: tc.function.arguments, - }, - }), - ); + data.tool_calls_unified || + data.tool_calls || + data.choices?.[0]?.message?.tool_calls; + + // Normalize to ToolCallDelta format (handles OpenAI, Anthropic, and unified formats) + const toolCalls: ToolCallDelta[] | undefined = rawToolCalls + ?.map((tc): ToolCallDelta | undefined => { + if ("function" in tc && tc.function?.name) { + // OpenAI or unified format + const args = tc.function.arguments; + return { + id: tc.id, + type: "function" as const, + function: { + name: tc.function.name, + arguments: + typeof args === "string" ? args : JSON.stringify(args ?? {}), + }, + }; + } else if ("name" in tc && typeof tc.name === "string") { + // Anthropic format + const args = + tc.text ?? (tc.input ? JSON.stringify(tc.input) : "{}"); + return { + id: tc.id, + type: "function" as const, + function: { + name: tc.name, + arguments: + typeof args === "string" ? args : JSON.stringify(args), + }, + }; + } + return undefined; + }) + .filter((tc): tc is ToolCallDelta => tc !== undefined); const assistantMessage: ChatMessage = { role: "assistant", diff --git a/packages/openai-adapters/src/apis/AskSage.ts b/packages/openai-adapters/src/apis/AskSage.ts index af0912e61fb..f79002b7a8c 100644 --- a/packages/openai-adapters/src/apis/AskSage.ts +++ b/packages/openai-adapters/src/apis/AskSage.ts @@ -1,4 +1,3 @@ -import { v4 as uuidv4 } from "uuid"; import { ChatCompletion, ChatCompletionChunk, @@ -12,13 +11,14 @@ import { EmbeddingCreateParams, Model, } from "openai/resources/index"; +import { v4 as uuidv4 } from "uuid"; import { AskSageConfig, - AskSageTool, - AskSageToolChoice, - AskSageToolCall, AskSageResponse, AskSageTokenResponse, + AskSageTool, + AskSageToolCall, + AskSageToolChoice, } from "../types.js"; import { chatChunk, chatChunkFromDelta, customFetch } from "../util.js"; import { @@ -311,6 +311,63 @@ export class AskSageApi implements BaseLlmApi { return requestBody; } + /** + * Normalize tool calls from any format (OpenAI, Anthropic, unified) to OpenAI format. + * Ask Sage returns different formats depending on the underlying model provider. + */ + private normalizeToolCalls( + data: AskSageResponse, + ): ChatCompletionMessageToolCall[] | undefined { + // Prefer tool_calls_unified (already normalized by Ask Sage server) + const rawToolCalls = + data.tool_calls_unified || + data.tool_calls || + data.choices?.[0]?.message?.tool_calls; + + if (!rawToolCalls || rawToolCalls.length === 0) { + return undefined; + } + + const normalized: ChatCompletionMessageToolCall[] = []; + + for (const tc of rawToolCalls) { + const toolCall = tc as AskSageToolCall; + + if ("function" in toolCall && toolCall.function?.name) { + // OpenAI format or unified format + const args = toolCall.function.arguments; + normalized.push({ + id: toolCall.id, + type: "function" as const, + function: { + name: toolCall.function.name, + arguments: + typeof args === "string" ? args : JSON.stringify(args ?? {}), + }, + }); + } else if ( + "name" in toolCall && + typeof (toolCall as any).name === "string" + ) { + // Anthropic format: {id, type: "tool_use", name, input, text} + const anthropicTc = toolCall as any; + const args = + anthropicTc.text ?? + (anthropicTc.input ? JSON.stringify(anthropicTc.input) : "{}"); + normalized.push({ + id: anthropicTc.id, + type: "function" as const, + function: { + name: anthropicTc.name, + arguments: typeof args === "string" ? args : JSON.stringify(args), + }, + }); + } + } + + return normalized.length > 0 ? normalized : undefined; + } + /** * Parse AskSage response into OpenAI ChatCompletion format */ @@ -323,20 +380,8 @@ export class AskSageApi implements BaseLlmApi { data.choices?.[0]?.message?.content || ""; - // Extract tool calls - const rawToolCalls = - data.tool_calls || data.choices?.[0]?.message?.tool_calls; - - const toolCalls: ChatCompletionMessageToolCall[] | undefined = rawToolCalls - ?.filter((tc) => tc.function?.name) // Filter out malformed tool calls - .map((tc) => ({ - id: tc.id, - type: "function" as const, - function: { - name: tc.function.name, - arguments: tc.function.arguments ?? "", - }, - })); + // Extract and normalize tool calls from any provider format + const toolCalls = this.normalizeToolCalls(data); return { id: uuidv4(), @@ -432,6 +477,7 @@ export class AskSageApi implements BaseLlmApi { } const data = (await response.json()) as AskSageResponse; + console.log("AskSage response:", JSON.stringify(data)); // Extract content const content = @@ -441,10 +487,8 @@ export class AskSageApi implements BaseLlmApi { data.choices?.[0]?.message?.content || ""; - // Extract tool calls and filter out malformed ones - const rawToolCalls = - data.tool_calls || data.choices?.[0]?.message?.tool_calls; - const validToolCalls = rawToolCalls?.filter((tc) => tc.function?.name); + // Extract and normalize tool calls from any provider format + const toolCalls = this.normalizeToolCalls(data); // Yield content as a single chunk if (content) { @@ -455,9 +499,12 @@ export class AskSageApi implements BaseLlmApi { } // Yield tool calls if present - if (validToolCalls && validToolCalls.length > 0) { - for (let i = 0; i < validToolCalls.length; i++) { - const tc = validToolCalls[i]; + if (toolCalls && toolCalls.length > 0) { + for (let i = 0; i < toolCalls.length; i++) { + const tc = toolCalls[i] as { + id: string; + function: { name: string; arguments: string }; + }; yield chatChunkFromDelta({ model: body.model, delta: { @@ -468,7 +515,7 @@ export class AskSageApi implements BaseLlmApi { type: "function", function: { name: tc.function.name, - arguments: tc.function.arguments ?? "", + arguments: tc.function.arguments, }, }, ], @@ -482,7 +529,7 @@ export class AskSageApi implements BaseLlmApi { content: null, model: body.model, finish_reason: - validToolCalls && validToolCalls.length > 0 ? "tool_calls" : "stop", + toolCalls && toolCalls.length > 0 ? "tool_calls" : "stop", }); } catch (error) { if (error instanceof Error) { diff --git a/packages/openai-adapters/src/test/asksage-adapter.vitest.ts b/packages/openai-adapters/src/test/asksage-adapter.vitest.ts index d449a5f552e..20d123cfe45 100644 --- a/packages/openai-adapters/src/test/asksage-adapter.vitest.ts +++ b/packages/openai-adapters/src/test/asksage-adapter.vitest.ts @@ -246,6 +246,90 @@ describe("AskSage Adapter Tests", () => { }); }); + test("chatCompletionNonStream should handle Anthropic-format tool calls from Claude models", async () => { + await runAdapterTest({ + config: { + provider: "askSage", + apiKey: "test-api-key", + apiBase: "https://api.asksage.ai/server", + }, + methodToTest: "chatCompletionNonStream", + params: [ + { + model: "claude-sonnet-4", + messages: [{ role: "user", content: "Read the test file" }], + tools: [ + { + type: "function", + function: { + name: "read_file", + description: "Read a file", + parameters: { + type: "object", + properties: { + filepath: { type: "string" }, + }, + }, + }, + }, + ], + }, + new AbortController().signal, + ], + expectedRequest: { + url: "https://api.asksage.ai/server/query", + method: "POST", + body: { + message: "Read the test file", + model: "claude-sonnet-4", + temperature: 0, + mode: "chat", + limit_references: 0, + tools: [ + { + type: "function", + function: { + name: "read_file", + description: "Read a file", + parameters: { + type: "object", + properties: { + filepath: { type: "string" }, + }, + }, + }, + }, + ], + }, + }, + // Anthropic-style response (type: "tool_use", name at top level, input as object) + mockResponse: { + message: "\n\n", + response: "OK", + status: 200, + tool_calls: [ + { + id: "toolu_vrtx_01ABC", + type: "tool_use", + name: "read_file", + input: { filepath: "test.py" }, + text: '{"filepath": "test.py"}', + }, + ], + tool_calls_unified: [ + { + id: "toolu_vrtx_01ABC", + type: "function", + function: { + name: "read_file", + arguments: { filepath: "test.py" }, + }, + }, + ], + }, + }); + }); + test("chatCompletionNonStream with multi-turn tool calling should preserve tool call messages", async () => { await runAdapterTest({ config: { diff --git a/packages/openai-adapters/src/types.ts b/packages/openai-adapters/src/types.ts index 3b324b0ac6b..817a936c58d 100644 --- a/packages/openai-adapters/src/types.ts +++ b/packages/openai-adapters/src/types.ts @@ -160,7 +160,10 @@ export type AskSageToolChoice = | "none" | { type: "function"; function: { name: string } }; -export interface AskSageToolCall { +/** + * OpenAI-style tool call (GPT models) + */ +export interface AskSageToolCallOpenAI { id: string; type: "function"; function: { @@ -169,6 +172,37 @@ export interface AskSageToolCall { }; } +/** + * Anthropic-style tool call (Claude models) + */ +export interface AskSageToolCallAnthropic { + id: string; + type: "tool_use"; + name: string; + input?: Record; + text?: string; +} + +/** + * Unified tool call format from Ask Sage (normalized across providers) + */ +export interface AskSageToolCallUnified { + id: string; + type: "function"; + function: { + name: string; + arguments: string | Record; + }; +} + +/** + * Any tool call format the Ask Sage API may return + */ +export type AskSageToolCall = + | AskSageToolCallOpenAI + | AskSageToolCallAnthropic + | AskSageToolCallUnified; + /** * AskSage API response format */ @@ -179,6 +213,7 @@ export interface AskSageResponse { status?: number | string; response?: unknown; tool_calls?: AskSageToolCall[]; + tool_calls_unified?: AskSageToolCallUnified[]; choices?: Array<{ message?: { content?: string; From 53356fc813b9277cb482339b0f06e334e27652b2 Mon Sep 17 00:00:00 2001 From: Alex McGraw Date: Tue, 5 May 2026 09:44:05 -0500 Subject: [PATCH 3/3] removed console log --- packages/openai-adapters/src/apis/AskSage.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/openai-adapters/src/apis/AskSage.ts b/packages/openai-adapters/src/apis/AskSage.ts index f79002b7a8c..153b4bcabd2 100644 --- a/packages/openai-adapters/src/apis/AskSage.ts +++ b/packages/openai-adapters/src/apis/AskSage.ts @@ -477,7 +477,6 @@ export class AskSageApi implements BaseLlmApi { } const data = (await response.json()) as AskSageResponse; - console.log("AskSage response:", JSON.stringify(data)); // Extract content const content =