mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add AI SDK dependencies and message conversion utilities (#11047)
This commit is contained in:
parent
f5004ac40a
commit
fe722dad23
6 changed files with 918 additions and 75 deletions
|
|
@ -66,7 +66,8 @@
|
|||
"bluebird": ">=3.7.2",
|
||||
"glob": ">=11.1.0",
|
||||
"@types/react": "^18.3.23",
|
||||
"@types/react-dom": "^18.3.5"
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"zod": "3.25.76"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
"clean": "rimraf dist .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.25.61"
|
||||
"zod": "3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@roo-code/config-eslint": "workspace:^",
|
||||
|
|
|
|||
209
pnpm-lock.yaml
generated
209
pnpm-lock.yaml
generated
|
|
@ -14,6 +14,7 @@ overrides:
|
|||
glob: '>=11.1.0'
|
||||
'@types/react': ^18.3.23
|
||||
'@types/react-dom': ^18.3.5
|
||||
zod: 3.25.76
|
||||
|
||||
importers:
|
||||
|
||||
|
|
@ -268,7 +269,7 @@ importers:
|
|||
version: 0.518.0(react@18.3.1)
|
||||
next:
|
||||
specifier: ~15.2.8
|
||||
version: 15.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 15.2.8(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next-themes:
|
||||
specifier: ^0.4.6
|
||||
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
|
@ -303,8 +304,8 @@ importers:
|
|||
specifier: ^1.1.2
|
||||
version: 1.1.2(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
zod:
|
||||
specifier: ^3.25.61
|
||||
version: 3.25.61
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@roo-code/config-eslint':
|
||||
specifier: workspace:^
|
||||
|
|
@ -377,7 +378,7 @@ importers:
|
|||
version: 0.518.0(react@18.3.1)
|
||||
next:
|
||||
specifier: ~15.2.8
|
||||
version: 15.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 15.2.8(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next-themes:
|
||||
specifier: ^0.4.6
|
||||
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
|
@ -418,8 +419,8 @@ importers:
|
|||
specifier: ^6.1.86
|
||||
version: 6.1.86
|
||||
zod:
|
||||
specifier: ^3.25.61
|
||||
version: 3.25.61
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@roo-code/config-eslint':
|
||||
specifier: workspace:^
|
||||
|
|
@ -444,7 +445,7 @@ importers:
|
|||
version: 10.4.21(postcss@8.5.4)
|
||||
next-sitemap:
|
||||
specifier: ^4.2.3
|
||||
version: 4.2.3(next@15.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
|
||||
version: 4.2.3(next@15.2.8(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
|
||||
postcss:
|
||||
specifier: ^8.5.4
|
||||
version: 8.5.4
|
||||
|
|
@ -455,8 +456,8 @@ importers:
|
|||
packages/build:
|
||||
dependencies:
|
||||
zod:
|
||||
specifier: ^3.25.61
|
||||
version: 3.25.61
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@roo-code/config-eslint':
|
||||
specifier: workspace:^
|
||||
|
|
@ -489,7 +490,7 @@ importers:
|
|||
specifier: ^4.8.1
|
||||
version: 4.8.1
|
||||
zod:
|
||||
specifier: ^3.25.76
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@roo-code/config-eslint':
|
||||
|
|
@ -564,7 +565,7 @@ importers:
|
|||
specifier: ^5.12.2
|
||||
version: 5.12.2(ws@8.18.3)(zod@3.25.76)
|
||||
zod:
|
||||
specifier: ^3.25.61
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@roo-code/config-eslint':
|
||||
|
|
@ -593,7 +594,7 @@ importers:
|
|||
version: 0.13.0
|
||||
drizzle-orm:
|
||||
specifier: ^0.44.1
|
||||
version: 0.44.1(@libsql/client@0.15.8)(better-sqlite3@11.10.0)(gel@2.1.0)(postgres@3.4.7)
|
||||
version: 0.44.1(@libsql/client@0.15.8)(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(gel@2.1.0)(postgres@3.4.7)
|
||||
execa:
|
||||
specifier: ^9.6.0
|
||||
version: 9.6.0
|
||||
|
|
@ -619,8 +620,8 @@ importers:
|
|||
specifier: ^5.5.5
|
||||
version: 5.5.5
|
||||
zod:
|
||||
specifier: ^3.25.61
|
||||
version: 3.25.61
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@roo-code/config-eslint':
|
||||
specifier: workspace:^
|
||||
|
|
@ -681,8 +682,8 @@ importers:
|
|||
specifier: ^5.0.0
|
||||
version: 5.1.1
|
||||
zod:
|
||||
specifier: ^3.25.61
|
||||
version: 3.25.61
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@roo-code/config-eslint':
|
||||
specifier: workspace:^
|
||||
|
|
@ -703,8 +704,8 @@ importers:
|
|||
packages/types:
|
||||
dependencies:
|
||||
zod:
|
||||
specifier: ^3.25.61
|
||||
version: 3.25.61
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@roo-code/config-eslint':
|
||||
specifier: workspace:^
|
||||
|
|
@ -765,7 +766,7 @@ importers:
|
|||
version: 1.2.0
|
||||
'@mistralai/mistralai':
|
||||
specifier: ^1.9.18
|
||||
version: 1.9.18(zod@3.25.61)
|
||||
version: 1.9.18(zod@3.25.76)
|
||||
'@modelcontextprotocol/sdk':
|
||||
specifier: 1.12.0
|
||||
version: 1.12.0
|
||||
|
|
@ -879,7 +880,7 @@ importers:
|
|||
version: 0.5.17
|
||||
openai:
|
||||
specifier: ^5.12.2
|
||||
version: 5.12.2(ws@8.18.3)(zod@3.25.61)
|
||||
version: 5.12.2(ws@8.18.3)(zod@3.25.76)
|
||||
os-name:
|
||||
specifier: ^6.0.0
|
||||
version: 6.1.0
|
||||
|
|
@ -986,9 +987,12 @@ importers:
|
|||
specifier: ^2.8.0
|
||||
version: 2.8.0
|
||||
zod:
|
||||
specifier: 3.25.61
|
||||
version: 3.25.61
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@openrouter/ai-sdk-provider':
|
||||
specifier: ^2.0.4
|
||||
version: 2.1.1(ai@6.0.57(zod@3.25.76))(zod@3.25.76)
|
||||
'@roo-code/build':
|
||||
specifier: workspace:^
|
||||
version: link:../packages/build
|
||||
|
|
@ -1013,9 +1017,6 @@ importers:
|
|||
'@types/glob':
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0
|
||||
'@types/json-stream-stringify':
|
||||
specifier: ^2.0.4
|
||||
version: 2.0.4
|
||||
'@types/lodash.debounce':
|
||||
specifier: ^4.0.9
|
||||
version: 4.0.9
|
||||
|
|
@ -1064,6 +1065,9 @@ importers:
|
|||
'@vscode/vsce':
|
||||
specifier: 3.3.2
|
||||
version: 3.3.2
|
||||
ai:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.57(zod@3.25.76)
|
||||
esbuild-wasm:
|
||||
specifier: ^0.25.0
|
||||
version: 0.25.12
|
||||
|
|
@ -1099,7 +1103,7 @@ importers:
|
|||
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.50)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
|
||||
zod-to-ts:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0(typescript@5.8.3)(zod@3.25.61)
|
||||
version: 1.2.0(typescript@5.8.3)(zod@3.25.76)
|
||||
|
||||
webview-ui:
|
||||
dependencies:
|
||||
|
|
@ -1305,8 +1309,8 @@ importers:
|
|||
specifier: ^0.2.2
|
||||
version: 0.2.2(@types/react@18.3.23)(react@18.3.1)
|
||||
zod:
|
||||
specifier: ^3.25.61
|
||||
version: 3.25.61
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@roo-code/config-eslint':
|
||||
specifier: workspace:^
|
||||
|
|
@ -1374,6 +1378,22 @@ packages:
|
|||
'@adobe/css-tools@4.4.2':
|
||||
resolution: {integrity: sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==}
|
||||
|
||||
'@ai-sdk/gateway@3.0.25':
|
||||
resolution: {integrity: sha512-j0AQeA7hOVqwImykQlganf/Euj3uEXf0h3G0O4qKTDpEwE+EZGIPnVimCWht5W91lAetPZSfavDyvfpuPDd2PQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider-utils@4.0.10':
|
||||
resolution: {integrity: sha512-VeDAiCH+ZK8Xs4hb9Cw7pHlujWNL52RKe8TExOkrw6Ir1AmfajBZTb9XUdKOZO08RwQElIKA8+Ltm+Gqfo8djQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider@3.0.5':
|
||||
resolution: {integrity: sha512-2Xmoq6DBJqmSl80U6V9z5jJSJP7ehaJJQMy2iFUqTay06wdCqTnPVBBQbtEL8RCChenL+q5DC5H5WzU3vV3v8w==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@alcalzone/ansi-tokenize@0.2.3':
|
||||
resolution: {integrity: sha512-jsElTJ0sQ4wHRz+C45tfect76BwbTbgkgKByOzpCN9xG61N5V6u/glvg1CsNJhq2xJIFpKHSwG3D2wPPuEYOrQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -2392,7 +2412,7 @@ packages:
|
|||
'@mistralai/mistralai@1.9.18':
|
||||
resolution: {integrity: sha512-D/vNAGEvWMsg95tzgLTg7pPnW9leOPyH+nh1Os05NwxVPbUykoYgMAwOEX7J46msahWdvZ4NQQuxUXIUV2P6dg==}
|
||||
peerDependencies:
|
||||
zod: '>= 3'
|
||||
zod: 3.25.76
|
||||
|
||||
'@mixmark-io/domino@2.2.0':
|
||||
resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==}
|
||||
|
|
@ -2591,6 +2611,17 @@ packages:
|
|||
'@open-draft/until@2.1.0':
|
||||
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
|
||||
|
||||
'@openrouter/ai-sdk-provider@2.1.1':
|
||||
resolution: {integrity: sha512-UypPbVnSExxmG/4Zg0usRiit3auvQVrjUXSyEhm0sZ9GQnW/d8p/bKgCk2neh1W5YyRSo7PNQvCrAEBHZnqQkQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
ai: ^6.0.0
|
||||
zod: 3.25.76
|
||||
|
||||
'@opentelemetry/api@1.9.0':
|
||||
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
'@oxc-resolver/binding-darwin-arm64@11.2.0':
|
||||
resolution: {integrity: sha512-ruKLkS+Dm/YIJaUhzEB7zPI+jh3EXxu0QnNV8I7t9jf0lpD2VnltuyRbhrbJEkksklZj//xCMyFFsILGjiU2Mg==}
|
||||
cpu: [arm64]
|
||||
|
|
@ -3845,6 +3876,9 @@ packages:
|
|||
'@socket.io/component-emitter@3.1.2':
|
||||
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
||||
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
'@standard-schema/utils@0.3.0':
|
||||
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
|
||||
|
||||
|
|
@ -4268,10 +4302,6 @@ packages:
|
|||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/json-stream-stringify@2.0.4':
|
||||
resolution: {integrity: sha512-xSFsVnoQ8Y/7BiVF3/fEIwRx9RoGzssDKVwhy1g23wkA4GAmA3v8lsl6CxsmUD6vf4EiRd+J0ULLkMbAWRSsgQ==}
|
||||
deprecated: This is a stub types definition. json-stream-stringify provides its own type definitions, so you do not need this installed.
|
||||
|
||||
'@types/katex@0.16.7':
|
||||
resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
|
||||
|
||||
|
|
@ -4469,6 +4499,10 @@ packages:
|
|||
resolution: {integrity: sha512-e4kQK9mP8ntpo3dACWirGod/hHv4qO5JMj9a/0a2AZto7b4persj5YP7t1Er372gTtYFTYxNhMx34jRvHooglw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
'@vercel/oidc@3.1.0':
|
||||
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@vitejs/plugin-react@4.4.1':
|
||||
resolution: {integrity: sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
|
@ -4621,6 +4655,12 @@ packages:
|
|||
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
|
||||
ai@6.0.57:
|
||||
resolution: {integrity: sha512-5wYcMQmOaNU71wGv4XX1db3zvn4uLjLbTKIo6cQZPWOJElA0882XI7Eawx6TCd5jbjOvKMIP+KLWbpVomAFT2g==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
|
||||
|
|
@ -6200,6 +6240,10 @@ packages:
|
|||
resolution: {integrity: sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
eventsource-parser@3.0.6:
|
||||
resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
eventsource@3.0.7:
|
||||
resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
|
@ -7319,6 +7363,9 @@ packages:
|
|||
json-schema-traverse@0.4.1:
|
||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||
|
||||
json-schema@0.4.0:
|
||||
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
|
||||
|
||||
json-stable-stringify-without-jsonify@1.0.1:
|
||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||
|
||||
|
|
@ -8317,7 +8364,7 @@ packages:
|
|||
hasBin: true
|
||||
peerDependencies:
|
||||
ws: ^8.18.0
|
||||
zod: ^3.23.8
|
||||
zod: 3.25.76
|
||||
peerDependenciesMeta:
|
||||
ws:
|
||||
optional: true
|
||||
|
|
@ -10688,25 +10735,19 @@ packages:
|
|||
zod-to-json-schema@3.24.5:
|
||||
resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==}
|
||||
peerDependencies:
|
||||
zod: ^3.24.1
|
||||
zod: 3.25.76
|
||||
|
||||
zod-to-ts@1.2.0:
|
||||
resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==}
|
||||
peerDependencies:
|
||||
typescript: ^4.9.4 || ^5.0.2
|
||||
zod: ^3
|
||||
zod: 3.25.76
|
||||
|
||||
zod-validation-error@3.4.1:
|
||||
resolution: {integrity: sha512-1KP64yqDPQ3rupxNv7oXhf7KdhHHgaqbKuspVoiN93TT0xrBjql+Svjkdjq/Qh/7GSMmgQs3AfvBT0heE35thw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
zod: ^3.24.4
|
||||
|
||||
zod@3.23.8:
|
||||
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
|
||||
|
||||
zod@3.25.61:
|
||||
resolution: {integrity: sha512-fzfJgUw78LTNnHujj9re1Ov/JJQkRZZGDMcYqSx7Hp4rPOkKywaFHq0S6GoHeXs0wGNE/sIOutkXgnwzrVOGCQ==}
|
||||
zod: 3.25.76
|
||||
|
||||
zod@3.25.76:
|
||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||
|
|
@ -10736,6 +10777,24 @@ snapshots:
|
|||
|
||||
'@adobe/css-tools@4.4.2': {}
|
||||
|
||||
'@ai-sdk/gateway@3.0.25(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.5
|
||||
'@ai-sdk/provider-utils': 4.0.10(zod@3.25.76)
|
||||
'@vercel/oidc': 3.1.0
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider-utils@4.0.10(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.5
|
||||
'@standard-schema/spec': 1.1.0
|
||||
eventsource-parser: 3.0.6
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider@3.0.5':
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
|
||||
'@alcalzone/ansi-tokenize@0.2.3':
|
||||
dependencies:
|
||||
ansi-styles: 6.2.3
|
||||
|
|
@ -12250,10 +12309,10 @@ snapshots:
|
|||
dependencies:
|
||||
exenv-es6: 1.1.1
|
||||
|
||||
'@mistralai/mistralai@1.9.18(zod@3.25.61)':
|
||||
'@mistralai/mistralai@1.9.18(zod@3.25.76)':
|
||||
dependencies:
|
||||
zod: 3.25.61
|
||||
zod-to-json-schema: 3.24.5(zod@3.25.61)
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.24.5(zod@3.25.76)
|
||||
|
||||
'@mixmark-io/domino@2.2.0': {}
|
||||
|
||||
|
|
@ -12421,6 +12480,13 @@ snapshots:
|
|||
|
||||
'@open-draft/until@2.1.0': {}
|
||||
|
||||
'@openrouter/ai-sdk-provider@2.1.1(ai@6.0.57(zod@3.25.76))(zod@3.25.76)':
|
||||
dependencies:
|
||||
ai: 6.0.57(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@opentelemetry/api@1.9.0': {}
|
||||
|
||||
'@oxc-resolver/binding-darwin-arm64@11.2.0':
|
||||
optional: true
|
||||
|
||||
|
|
@ -13822,6 +13888,8 @@ snapshots:
|
|||
|
||||
'@socket.io/component-emitter@3.1.2': {}
|
||||
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
||||
'@standard-schema/utils@0.3.0': {}
|
||||
|
||||
'@swc/counter@0.1.3': {}
|
||||
|
|
@ -14248,10 +14316,6 @@ snapshots:
|
|||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/json-stream-stringify@2.0.4':
|
||||
dependencies:
|
||||
json-stream-stringify: 3.1.6
|
||||
|
||||
'@types/katex@0.16.7': {}
|
||||
|
||||
'@types/lodash.debounce@4.0.9':
|
||||
|
|
@ -14483,6 +14547,8 @@ snapshots:
|
|||
satori: 0.12.2
|
||||
yoga-wasm-web: 0.3.3
|
||||
|
||||
'@vercel/oidc@3.1.0': {}
|
||||
|
||||
'@vitejs/plugin-react@4.4.1(vite@6.3.6(@types/node@20.17.57)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.27.1
|
||||
|
|
@ -14555,7 +14621,7 @@ snapshots:
|
|||
sirv: 3.0.1
|
||||
tinyglobby: 0.2.14
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.50)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
|
||||
|
||||
'@vitest/utils@3.2.4':
|
||||
dependencies:
|
||||
|
|
@ -14705,6 +14771,14 @@ snapshots:
|
|||
dependencies:
|
||||
humanize-ms: 1.2.1
|
||||
|
||||
ai@6.0.57(zod@3.25.76):
|
||||
dependencies:
|
||||
'@ai-sdk/gateway': 3.0.25(zod@3.25.76)
|
||||
'@ai-sdk/provider': 3.0.5
|
||||
'@ai-sdk/provider-utils': 4.0.10(zod@3.25.76)
|
||||
'@opentelemetry/api': 1.9.0
|
||||
zod: 3.25.76
|
||||
|
||||
ajv@6.12.6:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
|
|
@ -15232,7 +15306,7 @@ snapshots:
|
|||
dependencies:
|
||||
devtools-protocol: 0.0.1367902
|
||||
mitt: 3.0.1
|
||||
zod: 3.23.8
|
||||
zod: 3.25.76
|
||||
|
||||
chromium-bidi@5.1.0(devtools-protocol@0.0.1452169):
|
||||
dependencies:
|
||||
|
|
@ -15910,9 +15984,10 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
drizzle-orm@0.44.1(@libsql/client@0.15.8)(better-sqlite3@11.10.0)(gel@2.1.0)(postgres@3.4.7):
|
||||
drizzle-orm@0.44.1(@libsql/client@0.15.8)(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(gel@2.1.0)(postgres@3.4.7):
|
||||
optionalDependencies:
|
||||
'@libsql/client': 0.15.8
|
||||
'@opentelemetry/api': 1.9.0
|
||||
better-sqlite3: 11.10.0
|
||||
gel: 2.1.0
|
||||
postgres: 3.4.7
|
||||
|
|
@ -16387,6 +16462,8 @@ snapshots:
|
|||
|
||||
eventsource-parser@3.0.2: {}
|
||||
|
||||
eventsource-parser@3.0.6: {}
|
||||
|
||||
eventsource@3.0.7:
|
||||
dependencies:
|
||||
eventsource-parser: 3.0.2
|
||||
|
|
@ -17689,6 +17766,8 @@ snapshots:
|
|||
|
||||
json-schema-traverse@0.4.1: {}
|
||||
|
||||
json-schema@0.4.0: {}
|
||||
|
||||
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||
|
||||
json-stream-stringify@3.1.6: {}
|
||||
|
|
@ -18740,20 +18819,20 @@ snapshots:
|
|||
|
||||
netmask@2.0.2: {}
|
||||
|
||||
next-sitemap@4.2.3(next@15.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
|
||||
next-sitemap@4.2.3(next@15.2.8(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
|
||||
dependencies:
|
||||
'@corex/deepmerge': 4.0.43
|
||||
'@next/env': 13.5.11
|
||||
fast-glob: 3.3.3
|
||||
minimist: 1.2.8
|
||||
next: 15.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next: 15.2.8(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
||||
next-themes@0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
next@15.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
next@15.2.8(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@next/env': 15.2.8
|
||||
'@swc/counter': 0.1.3
|
||||
|
|
@ -18773,6 +18852,7 @@ snapshots:
|
|||
'@next/swc-linux-x64-musl': 15.2.5
|
||||
'@next/swc-win32-arm64-msvc': 15.2.5
|
||||
'@next/swc-win32-x64-msvc': 15.2.5
|
||||
'@opentelemetry/api': 1.9.0
|
||||
sharp: 0.33.5
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
|
|
@ -18945,11 +19025,6 @@ snapshots:
|
|||
is-inside-container: 1.0.0
|
||||
is-wsl: 3.1.0
|
||||
|
||||
openai@5.12.2(ws@8.18.3)(zod@3.25.61):
|
||||
optionalDependencies:
|
||||
ws: 8.18.3
|
||||
zod: 3.25.61
|
||||
|
||||
openai@5.12.2(ws@8.18.3)(zod@3.25.76):
|
||||
optionalDependencies:
|
||||
ws: 8.18.3
|
||||
|
|
@ -21785,27 +21860,19 @@ snapshots:
|
|||
compress-commons: 6.0.2
|
||||
readable-stream: 4.7.0
|
||||
|
||||
zod-to-json-schema@3.24.5(zod@3.25.61):
|
||||
dependencies:
|
||||
zod: 3.25.61
|
||||
|
||||
zod-to-json-schema@3.24.5(zod@3.25.76):
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.25.61):
|
||||
zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.25.76):
|
||||
dependencies:
|
||||
typescript: 5.8.3
|
||||
zod: 3.25.61
|
||||
zod: 3.25.76
|
||||
|
||||
zod-validation-error@3.4.1(zod@3.25.76):
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
zod@3.23.8: {}
|
||||
|
||||
zod@3.25.61: {}
|
||||
|
||||
zod@3.25.76: {}
|
||||
|
||||
zustand@5.0.9(@types/react@18.3.23)(react@19.2.3):
|
||||
|
|
|
|||
492
src/api/transform/__tests__/ai-sdk.spec.ts
Normal file
492
src/api/transform/__tests__/ai-sdk.spec.ts
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { convertToAiSdkMessages, convertToolsForAiSdk, processAiSdkStreamPart } from "../ai-sdk"
|
||||
|
||||
vitest.mock("ai", () => ({
|
||||
tool: vitest.fn((t) => t),
|
||||
jsonSchema: vitest.fn((s) => s),
|
||||
}))
|
||||
|
||||
describe("AI SDK conversion utilities", () => {
|
||||
describe("convertToAiSdkMessages", () => {
|
||||
it("converts simple string messages", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{ role: "user", content: "Hello" },
|
||||
{ role: "assistant", content: "Hi there" },
|
||||
]
|
||||
|
||||
const result = convertToAiSdkMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0]).toEqual({ role: "user", content: "Hello" })
|
||||
expect(result[1]).toEqual({ role: "assistant", content: "Hi there" })
|
||||
})
|
||||
|
||||
it("converts user messages with text content blocks", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Hello world" }],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToAiSdkMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0]).toEqual({
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Hello world" }],
|
||||
})
|
||||
})
|
||||
|
||||
it("converts user messages with image content", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "What is in this image?" },
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "base64",
|
||||
media_type: "image/png",
|
||||
data: "base64encodeddata",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToAiSdkMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0]).toEqual({
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "What is in this image?" },
|
||||
{
|
||||
type: "image",
|
||||
image: "data:image/png;base64,base64encodeddata",
|
||||
mimeType: "image/png",
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("converts user messages with URL image content", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "What is in this image?" },
|
||||
{
|
||||
type: "image",
|
||||
source: {
|
||||
type: "url",
|
||||
url: "https://example.com/image.png",
|
||||
},
|
||||
} as any,
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToAiSdkMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0]).toEqual({
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "What is in this image?" },
|
||||
{
|
||||
type: "image",
|
||||
image: "https://example.com/image.png",
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("converts tool results into separate tool role messages with resolved tool names", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "call_123",
|
||||
name: "read_file",
|
||||
input: { path: "test.ts" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "call_123",
|
||||
content: "Tool result content",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToAiSdkMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0]).toEqual({
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "call_123",
|
||||
toolName: "read_file",
|
||||
input: { path: "test.ts" },
|
||||
},
|
||||
],
|
||||
})
|
||||
// Tool results now go to role: "tool" messages per AI SDK v6 schema
|
||||
expect(result[1]).toEqual({
|
||||
role: "tool",
|
||||
content: [
|
||||
{
|
||||
type: "tool-result",
|
||||
toolCallId: "call_123",
|
||||
toolName: "read_file",
|
||||
output: { type: "text", value: "Tool result content" },
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("uses unknown_tool for tool results without matching tool call", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "call_orphan",
|
||||
content: "Orphan result",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToAiSdkMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
// Tool results go to role: "tool" messages
|
||||
expect(result[0]).toEqual({
|
||||
role: "tool",
|
||||
content: [
|
||||
{
|
||||
type: "tool-result",
|
||||
toolCallId: "call_orphan",
|
||||
toolName: "unknown_tool",
|
||||
output: { type: "text", value: "Orphan result" },
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("separates tool results and text content into different messages", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "call_123",
|
||||
name: "read_file",
|
||||
input: { path: "test.ts" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "call_123",
|
||||
content: "File contents here",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "Please analyze this file",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToAiSdkMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(3)
|
||||
expect(result[0]).toEqual({
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "call_123",
|
||||
toolName: "read_file",
|
||||
input: { path: "test.ts" },
|
||||
},
|
||||
],
|
||||
})
|
||||
// Tool results go first in a "tool" message
|
||||
expect(result[1]).toEqual({
|
||||
role: "tool",
|
||||
content: [
|
||||
{
|
||||
type: "tool-result",
|
||||
toolCallId: "call_123",
|
||||
toolName: "read_file",
|
||||
output: { type: "text", value: "File contents here" },
|
||||
},
|
||||
],
|
||||
})
|
||||
// Text content goes in a separate "user" message
|
||||
expect(result[2]).toEqual({
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Please analyze this file" }],
|
||||
})
|
||||
})
|
||||
|
||||
it("converts assistant messages with tool use", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "text", text: "Let me read that file" },
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "call_456",
|
||||
name: "read_file",
|
||||
input: { path: "test.ts" },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToAiSdkMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0]).toEqual({
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "text", text: "Let me read that file" },
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "call_456",
|
||||
toolName: "read_file",
|
||||
input: { path: "test.ts" },
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("handles empty assistant content", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToAiSdkMessages(messages)
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0]).toEqual({
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "" }],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("convertToolsForAiSdk", () => {
|
||||
it("returns undefined for empty tools", () => {
|
||||
expect(convertToolsForAiSdk(undefined)).toBeUndefined()
|
||||
expect(convertToolsForAiSdk([])).toBeUndefined()
|
||||
})
|
||||
|
||||
it("converts function tools to AI SDK format", () => {
|
||||
const tools: OpenAI.Chat.ChatCompletionTool[] = [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "read_file",
|
||||
description: "Read a file from disk",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
path: { type: "string", description: "File path" },
|
||||
},
|
||||
required: ["path"],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToolsForAiSdk(tools)
|
||||
|
||||
expect(result).toBeDefined()
|
||||
expect(result!.read_file).toBeDefined()
|
||||
expect(result!.read_file.description).toBe("Read a file from disk")
|
||||
})
|
||||
|
||||
it("converts multiple tools", () => {
|
||||
const tools: OpenAI.Chat.ChatCompletionTool[] = [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "read_file",
|
||||
description: "Read a file",
|
||||
parameters: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "write_file",
|
||||
description: "Write a file",
|
||||
parameters: {},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToolsForAiSdk(tools)
|
||||
|
||||
expect(result).toBeDefined()
|
||||
expect(Object.keys(result!)).toHaveLength(2)
|
||||
expect(result!.read_file).toBeDefined()
|
||||
expect(result!.write_file).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("processAiSdkStreamPart", () => {
|
||||
it("processes text-delta chunks", () => {
|
||||
const part = { type: "text-delta" as const, id: "1", text: "Hello" }
|
||||
const chunks = [...processAiSdkStreamPart(part)]
|
||||
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(chunks[0]).toEqual({ type: "text", text: "Hello" })
|
||||
})
|
||||
|
||||
it("processes text chunks (fullStream format)", () => {
|
||||
const part = { type: "text" as const, text: "Hello from fullStream" }
|
||||
const chunks = [...processAiSdkStreamPart(part as any)]
|
||||
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(chunks[0]).toEqual({ type: "text", text: "Hello from fullStream" })
|
||||
})
|
||||
|
||||
it("processes reasoning-delta chunks", () => {
|
||||
const part = { type: "reasoning-delta" as const, id: "1", text: "thinking..." }
|
||||
const chunks = [...processAiSdkStreamPart(part)]
|
||||
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(chunks[0]).toEqual({ type: "reasoning", text: "thinking..." })
|
||||
})
|
||||
|
||||
it("processes reasoning chunks (fullStream format)", () => {
|
||||
const part = { type: "reasoning" as const, text: "reasoning from fullStream" }
|
||||
const chunks = [...processAiSdkStreamPart(part as any)]
|
||||
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(chunks[0]).toEqual({ type: "reasoning", text: "reasoning from fullStream" })
|
||||
})
|
||||
|
||||
it("processes tool-input-start chunks", () => {
|
||||
const part = { type: "tool-input-start" as const, id: "call_1", toolName: "read_file" }
|
||||
const chunks = [...processAiSdkStreamPart(part)]
|
||||
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(chunks[0]).toEqual({ type: "tool_call_start", id: "call_1", name: "read_file" })
|
||||
})
|
||||
|
||||
it("processes tool-input-delta chunks", () => {
|
||||
const part = { type: "tool-input-delta" as const, id: "call_1", delta: '{"path":' }
|
||||
const chunks = [...processAiSdkStreamPart(part)]
|
||||
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(chunks[0]).toEqual({ type: "tool_call_delta", id: "call_1", delta: '{"path":' })
|
||||
})
|
||||
|
||||
it("processes tool-input-end chunks", () => {
|
||||
const part = { type: "tool-input-end" as const, id: "call_1" }
|
||||
const chunks = [...processAiSdkStreamPart(part)]
|
||||
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(chunks[0]).toEqual({ type: "tool_call_end", id: "call_1" })
|
||||
})
|
||||
|
||||
it("processes complete tool-call chunks", () => {
|
||||
const part = {
|
||||
type: "tool-call" as const,
|
||||
toolCallId: "call_1",
|
||||
toolName: "read_file",
|
||||
input: { path: "test.ts" },
|
||||
}
|
||||
const chunks = [...processAiSdkStreamPart(part)]
|
||||
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(chunks[0]).toEqual({
|
||||
type: "tool_call",
|
||||
id: "call_1",
|
||||
name: "read_file",
|
||||
arguments: '{"path":"test.ts"}',
|
||||
})
|
||||
})
|
||||
|
||||
it("processes source chunks with URL", () => {
|
||||
const part = {
|
||||
type: "source" as const,
|
||||
url: "https://example.com",
|
||||
title: "Example Source",
|
||||
}
|
||||
const chunks = [...processAiSdkStreamPart(part as any)]
|
||||
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(chunks[0]).toEqual({
|
||||
type: "grounding",
|
||||
sources: [
|
||||
{
|
||||
title: "Example Source",
|
||||
url: "https://example.com",
|
||||
snippet: undefined,
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("processes error chunks", () => {
|
||||
const part = { type: "error" as const, error: new Error("Test error") }
|
||||
const chunks = [...processAiSdkStreamPart(part)]
|
||||
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(chunks[0]).toEqual({
|
||||
type: "error",
|
||||
error: "StreamError",
|
||||
message: "Test error",
|
||||
})
|
||||
})
|
||||
|
||||
it("ignores lifecycle events", () => {
|
||||
const lifecycleEvents = [
|
||||
{ type: "text-start" as const },
|
||||
{ type: "text-end" as const },
|
||||
{ type: "reasoning-start" as const },
|
||||
{ type: "reasoning-end" as const },
|
||||
{ type: "start-step" as const },
|
||||
{ type: "finish-step" as const },
|
||||
{ type: "start" as const },
|
||||
{ type: "finish" as const },
|
||||
{ type: "abort" as const },
|
||||
]
|
||||
|
||||
for (const event of lifecycleEvents) {
|
||||
const chunks = [...processAiSdkStreamPart(event as any)]
|
||||
expect(chunks).toHaveLength(0)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
282
src/api/transform/ai-sdk.ts
Normal file
282
src/api/transform/ai-sdk.ts
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
/**
|
||||
* AI SDK conversion utilities for transforming between Anthropic/OpenAI formats and Vercel AI SDK formats.
|
||||
* These utilities are designed to be reused across different AI SDK providers.
|
||||
*/
|
||||
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { tool as createTool, jsonSchema, type ModelMessage, type TextStreamPart } from "ai"
|
||||
import type { ApiStreamChunk } from "./stream"
|
||||
|
||||
/**
|
||||
* Convert Anthropic messages to AI SDK ModelMessage format.
|
||||
* Handles text, images, tool uses, and tool results.
|
||||
*
|
||||
* @param messages - Array of Anthropic message parameters
|
||||
* @returns Array of AI SDK ModelMessage objects
|
||||
*/
|
||||
export function convertToAiSdkMessages(messages: Anthropic.Messages.MessageParam[]): ModelMessage[] {
|
||||
const modelMessages: ModelMessage[] = []
|
||||
|
||||
// First pass: build a map of tool call IDs to tool names from assistant messages
|
||||
const toolCallIdToName = new Map<string, string>()
|
||||
for (const message of messages) {
|
||||
if (message.role === "assistant" && typeof message.content !== "string") {
|
||||
for (const part of message.content) {
|
||||
if (part.type === "tool_use") {
|
||||
toolCallIdToName.set(part.id, part.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const message of messages) {
|
||||
if (typeof message.content === "string") {
|
||||
modelMessages.push({
|
||||
role: message.role,
|
||||
content: message.content,
|
||||
})
|
||||
} else {
|
||||
if (message.role === "user") {
|
||||
const parts: Array<
|
||||
{ type: "text"; text: string } | { type: "image"; image: string; mimeType?: string }
|
||||
> = []
|
||||
const toolResults: Array<{
|
||||
type: "tool-result"
|
||||
toolCallId: string
|
||||
toolName: string
|
||||
output: { type: "text"; value: string }
|
||||
}> = []
|
||||
|
||||
for (const part of message.content) {
|
||||
if (part.type === "text") {
|
||||
parts.push({ type: "text", text: part.text })
|
||||
} else if (part.type === "image") {
|
||||
// Handle both base64 and URL source types
|
||||
const source = part.source as { type: string; media_type?: string; data?: string; url?: string }
|
||||
if (source.type === "base64" && source.media_type && source.data) {
|
||||
parts.push({
|
||||
type: "image",
|
||||
image: `data:${source.media_type};base64,${source.data}`,
|
||||
mimeType: source.media_type,
|
||||
})
|
||||
} else if (source.type === "url" && source.url) {
|
||||
parts.push({
|
||||
type: "image",
|
||||
image: source.url,
|
||||
})
|
||||
}
|
||||
} else if (part.type === "tool_result") {
|
||||
// Convert tool results to string content
|
||||
let content: string
|
||||
if (typeof part.content === "string") {
|
||||
content = part.content
|
||||
} else {
|
||||
content =
|
||||
part.content
|
||||
?.map((c) => {
|
||||
if (c.type === "text") return c.text
|
||||
if (c.type === "image") return "(image)"
|
||||
return ""
|
||||
})
|
||||
.join("\n") ?? ""
|
||||
}
|
||||
// Look up the tool name from the tool call ID
|
||||
const toolName = toolCallIdToName.get(part.tool_use_id) ?? "unknown_tool"
|
||||
toolResults.push({
|
||||
type: "tool-result",
|
||||
toolCallId: part.tool_use_id,
|
||||
toolName,
|
||||
output: { type: "text", value: content || "(empty)" },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// AI SDK requires tool results in separate "tool" role messages
|
||||
// UserContent only supports: string | Array<TextPart | ImagePart | FilePart>
|
||||
// ToolContent (for role: "tool") supports: Array<ToolResultPart | ToolApprovalResponse>
|
||||
if (toolResults.length > 0) {
|
||||
modelMessages.push({
|
||||
role: "tool",
|
||||
content: toolResults,
|
||||
} as ModelMessage)
|
||||
}
|
||||
|
||||
// Add user message with only text/image content (no tool results)
|
||||
if (parts.length > 0) {
|
||||
modelMessages.push({
|
||||
role: "user",
|
||||
content: parts,
|
||||
} as ModelMessage)
|
||||
}
|
||||
} else if (message.role === "assistant") {
|
||||
const textParts: string[] = []
|
||||
const toolCalls: Array<{
|
||||
type: "tool-call"
|
||||
toolCallId: string
|
||||
toolName: string
|
||||
input: unknown
|
||||
}> = []
|
||||
|
||||
for (const part of message.content) {
|
||||
if (part.type === "text") {
|
||||
textParts.push(part.text)
|
||||
} else if (part.type === "tool_use") {
|
||||
toolCalls.push({
|
||||
type: "tool-call",
|
||||
toolCallId: part.id,
|
||||
toolName: part.name,
|
||||
input: part.input,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const content: Array<
|
||||
| { type: "text"; text: string }
|
||||
| { type: "tool-call"; toolCallId: string; toolName: string; input: unknown }
|
||||
> = []
|
||||
|
||||
if (textParts.length > 0) {
|
||||
content.push({ type: "text", text: textParts.join("\n") })
|
||||
}
|
||||
content.push(...toolCalls)
|
||||
|
||||
modelMessages.push({
|
||||
role: "assistant",
|
||||
content: content.length > 0 ? content : [{ type: "text", text: "" }],
|
||||
} as ModelMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modelMessages
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert OpenAI-style function tool definitions to AI SDK tool format.
|
||||
*
|
||||
* @param tools - Array of OpenAI tool definitions
|
||||
* @returns Record of AI SDK tools keyed by tool name, or undefined if no tools
|
||||
*/
|
||||
export function convertToolsForAiSdk(
|
||||
tools: OpenAI.Chat.ChatCompletionTool[] | undefined,
|
||||
): Record<string, ReturnType<typeof createTool>> | undefined {
|
||||
if (!tools || tools.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const toolSet: Record<string, ReturnType<typeof createTool>> = {}
|
||||
|
||||
for (const t of tools) {
|
||||
if (t.type === "function") {
|
||||
toolSet[t.function.name] = createTool({
|
||||
description: t.function.description,
|
||||
inputSchema: jsonSchema(t.function.parameters as any),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return toolSet
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended stream part type that includes additional fullStream event types
|
||||
* that are emitted at runtime but not included in the AI SDK TextStreamPart type definitions.
|
||||
*/
|
||||
type ExtendedStreamPart = TextStreamPart<any> | { type: "text"; text: string } | { type: "reasoning"; text: string }
|
||||
|
||||
/**
|
||||
* Process a single AI SDK stream part and yield the appropriate ApiStreamChunk(s).
|
||||
* This generator handles all TextStreamPart types and converts them to the
|
||||
* ApiStreamChunk format used by the application.
|
||||
*
|
||||
* @param part - The AI SDK TextStreamPart to process (including fullStream event types)
|
||||
* @yields ApiStreamChunk objects corresponding to the stream part
|
||||
*/
|
||||
export function* processAiSdkStreamPart(part: ExtendedStreamPart): Generator<ApiStreamChunk> {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
case "text-delta":
|
||||
yield { type: "text", text: (part as { text: string }).text }
|
||||
break
|
||||
|
||||
case "reasoning":
|
||||
case "reasoning-delta":
|
||||
yield { type: "reasoning", text: (part as { text: string }).text }
|
||||
break
|
||||
|
||||
case "tool-input-start":
|
||||
yield {
|
||||
type: "tool_call_start",
|
||||
id: part.id,
|
||||
name: part.toolName,
|
||||
}
|
||||
break
|
||||
|
||||
case "tool-input-delta":
|
||||
yield {
|
||||
type: "tool_call_delta",
|
||||
id: part.id,
|
||||
delta: part.delta,
|
||||
}
|
||||
break
|
||||
|
||||
case "tool-input-end":
|
||||
yield {
|
||||
type: "tool_call_end",
|
||||
id: part.id,
|
||||
}
|
||||
break
|
||||
|
||||
case "tool-call":
|
||||
// Complete tool call - emit for compatibility
|
||||
yield {
|
||||
type: "tool_call",
|
||||
id: part.toolCallId,
|
||||
name: part.toolName,
|
||||
arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input),
|
||||
}
|
||||
break
|
||||
|
||||
case "source":
|
||||
// Handle both URL and document source types
|
||||
if ("url" in part) {
|
||||
yield {
|
||||
type: "grounding",
|
||||
sources: [
|
||||
{
|
||||
title: part.title || "Source",
|
||||
url: part.url,
|
||||
snippet: undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case "error":
|
||||
yield {
|
||||
type: "error",
|
||||
error: "StreamError",
|
||||
message: part.error instanceof Error ? part.error.message : String(part.error),
|
||||
}
|
||||
break
|
||||
|
||||
// Ignore lifecycle events that don't need to yield chunks
|
||||
case "text-start":
|
||||
case "text-end":
|
||||
case "reasoning-start":
|
||||
case "reasoning-end":
|
||||
case "start-step":
|
||||
case "finish-step":
|
||||
case "start":
|
||||
case "finish":
|
||||
case "abort":
|
||||
case "file":
|
||||
case "tool-result":
|
||||
case "tool-error":
|
||||
case "raw":
|
||||
// These events don't need to be yielded
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -529,9 +529,10 @@
|
|||
"web-tree-sitter": "^0.25.6",
|
||||
"workerpool": "^9.2.0",
|
||||
"yaml": "^2.8.0",
|
||||
"zod": "3.25.61"
|
||||
"zod": "3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openrouter/ai-sdk-provider": "^2.0.4",
|
||||
"@roo-code/build": "workspace:^",
|
||||
"@roo-code/config-eslint": "workspace:^",
|
||||
"@roo-code/config-typescript": "workspace:^",
|
||||
|
|
@ -540,7 +541,6 @@
|
|||
"@types/diff": "^5.2.1",
|
||||
"@types/diff-match-patch": "^1.0.36",
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/json-stream-stringify": "^2.0.4",
|
||||
"@types/lodash.debounce": "^4.0.9",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "20.x",
|
||||
|
|
@ -557,6 +557,7 @@
|
|||
"@types/vscode": "^1.84.0",
|
||||
"@vscode/test-electron": "^2.5.2",
|
||||
"@vscode/vsce": "3.3.2",
|
||||
"ai": "^6.0.0",
|
||||
"esbuild-wasm": "^0.25.0",
|
||||
"execa": "^9.5.2",
|
||||
"glob": "^11.1.0",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue