Add repository guidelines and update AI SDK to v5 (#5)
# Update AI SDK to v5 and add repository guidelines This PR updates the project to use AI SDK v5 and adds comprehensive repository guidelines. Key changes include: 1. Added `AGENTS.md` with detailed repository guidelines covering: - Project structure and organization - Build and development commands - Coding style conventions - Testing guidelines - Commit and PR best practices - Security considerations 2. Updated AI SDK dependencies: - Upgraded from v4 to v5 (`ai@5.0.8`) - Updated all provider packages to v2 series - Added explicit `zod` dependency with pinned version 3. Improved build script to: - Properly externalize dependencies - Add shebang line more reliably - Make output executable 4. Updated code to support AI SDK v5 API changes: - Migrated from `ProviderV1` to `ProviderV2` interfaces - Updated message conversion logic - Fixed stream handling for new event types - Made `signature` field optional in thinking content 5. Updated examples in documentation to reference newer models like `gpt-5-mini` instead of older models. These changes ensure compatibility with the latest AI SDK while providing clear guidelines for contributors.
This commit is contained in:
commit
4050bf66e5
12 changed files with 484 additions and 456 deletions
39
AGENTS.md
Normal file
39
AGENTS.md
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Repository Guidelines
|
||||||
|
|
||||||
|
## Project Structure & Module Organization
|
||||||
|
- `src/`: TypeScript sources. Key files: `main.ts` (CLI entry), `anthropic-proxy.ts` (HTTP proxy), `convert-*.ts` (format converters), `detect-mimetype.ts`, `json-schema.ts`.
|
||||||
|
- `dist/`: Bundled CLI entry `main.js` (created by build).
|
||||||
|
- `package.json`, `bun.lock`: Bun-based build and deps; `bin.anyclaude` points to `dist/main.js`.
|
||||||
|
- `flake.nix`, `.envrc`: Nix/direnv developer shell and formatters.
|
||||||
|
- Assets/config: `README.md`, `CLAUDE.md`, `tsconfig.json`, `demo.png`.
|
||||||
|
|
||||||
|
## Build, Test, and Development Commands
|
||||||
|
- Install: `bun install`.
|
||||||
|
- Build: `bun run build` (outputs `dist/main.js` with Node shebang).
|
||||||
|
- Run CLI (after build): `./dist/main.js --model openai/gpt-5-mini`.
|
||||||
|
- Dev run (no build):
|
||||||
|
- `PROXY_ONLY=true bun run src/main.ts` (prints proxy URL)
|
||||||
|
- `OPENAI_API_KEY=... bun run src/main.ts --model openai/gpt-5-mini`
|
||||||
|
- Nix shell: `direnv allow` (or `nix develop`); format Nix/shell files with `nix fmt`.
|
||||||
|
|
||||||
|
## Coding Style & Naming Conventions
|
||||||
|
- Language: TypeScript (ESNext, strict mode enabled).
|
||||||
|
- Indentation: 2 spaces; keep lines reasonable; use explicit imports (`verbatimModuleSyntax`).
|
||||||
|
- Files: kebab-case `.ts` (e.g., `convert-to-anthropic-stream.ts`).
|
||||||
|
- Names: `camelCase` for variables/functions; `PascalCase` for types/classes.
|
||||||
|
- Exports: prefer named exports; keep modules single‑purpose and small.
|
||||||
|
|
||||||
|
## Testing Guidelines
|
||||||
|
- No test runner is configured yet. If adding tests:
|
||||||
|
- Place under `src/**/*.test.ts` or `src/__tests__/`.
|
||||||
|
- Prioritize pure units (converters, schema, MIME detection). Avoid live provider calls by default; gate with env vars.
|
||||||
|
- Add a `test` script in `package.json` and document how to run it in `README.md`.
|
||||||
|
|
||||||
|
## Commit & Pull Request Guidelines
|
||||||
|
- Commits: imperative, concise subjects (e.g., "Add Nix development environment and Claude guidance file"). Include rationale in the body when helpful.
|
||||||
|
- PRs: clear description, linked issues, commands used to verify (with relevant env vars), and expected behavior. Avoid committing secrets; scrub logs.
|
||||||
|
- Keep scope small; update `README.md`/`CLAUDE.md` when behavior or env vars change.
|
||||||
|
|
||||||
|
## Security & Configuration Tips
|
||||||
|
- Never commit API keys. Use `direnv` for local secrets and keep `.envrc` minimal.
|
||||||
|
- Provider envs: `OPENAI_*`, `GOOGLE_*`, `XAI_*`, `AZURE_*`, optional `ANTHROPIC_*`. Use `PROXY_ONLY=true` to inspect the proxy without launching Claude.
|
||||||
|
|
@ -48,7 +48,7 @@ Test the proxy manually:
|
||||||
PROXY_ONLY=true bun run src/main.ts
|
PROXY_ONLY=true bun run src/main.ts
|
||||||
|
|
||||||
# Test with a provider
|
# Test with a provider
|
||||||
OPENAI_API_KEY=your-key bun run src/main.ts --model openai/gpt-4
|
OPENAI_API_KEY=your-key bun run src/main.ts --model openai/gpt-5-mini
|
||||||
```
|
```
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
@ -64,4 +64,3 @@ Required for each provider:
|
||||||
Special modes:
|
Special modes:
|
||||||
|
|
||||||
- `PROXY_ONLY=true`: Run proxy server without spawning Claude Code
|
- `PROXY_ONLY=true`: Run proxy server without spawning Claude Code
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ $ pnpm install -g anyclaude
|
||||||
|
|
||||||
# anyclaude is a wrapper for the Claude CLI
|
# anyclaude is a wrapper for the Claude CLI
|
||||||
# `openai/`, `google/`, `xai/`, and `anthropic/` are supported
|
# `openai/`, `google/`, `xai/`, and `anthropic/` are supported
|
||||||
$ anyclaude --model openai/o3
|
$ anyclaude --model openai/gpt-5-mini
|
||||||
```
|
```
|
||||||
|
|
||||||
Switch models in the Claude UI with `/model openai/o3`.
|
Switch models in the Claude UI with `/model openai/gpt-5-mini`.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
|
|
|
||||||
61
bun.lock
61
bun.lock
|
|
@ -4,15 +4,16 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "openclaude",
|
"name": "openclaude",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ai-sdk/anthropic": "^1.2.12",
|
"@ai-sdk/anthropic": "^2.0.1",
|
||||||
"@ai-sdk/azure": "^1.3.23",
|
"@ai-sdk/azure": "^2.0.6",
|
||||||
"@ai-sdk/google": "^1.2.18",
|
"@ai-sdk/google": "^2.0.3",
|
||||||
"@ai-sdk/openai": "^1.3.22",
|
"@ai-sdk/openai": "^2.0.6",
|
||||||
"@ai-sdk/xai": "^1.2.16",
|
"@ai-sdk/xai": "^2.0.3",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/json-schema": "^7.0.15",
|
"@types/json-schema": "^7.0.15",
|
||||||
"ai": "^4.3.16",
|
"ai": "^5.0.8",
|
||||||
"json-schema": "^0.4.0",
|
"json-schema": "^0.4.0",
|
||||||
|
"zod": "3.25.76",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
|
|
@ -20,67 +21,47 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@1.2.12", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-YSzjlko7JvuiyQFmI9RN1tNZdEiZxc+6xld/0tq/VkJaHpEzGAb1yiNxxvmYVcjvfu/PcvCxAAYXmTYQQ63IHQ=="],
|
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-HtNbpNV9qXQosHu00+CBMEcdTerwZY+kpVMNak0xP/P5TF6XkPf7IyizhLuc7y5zcXMjZCMA7jDGkcEdZCEdkw=="],
|
||||||
|
|
||||||
"@ai-sdk/azure": ["@ai-sdk/azure@1.3.23", "", { "dependencies": { "@ai-sdk/openai": "1.3.22", "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-vpsaPtU24RBVk/IMM5UylR/N4RtAuL2NZLWc7LJ3tvMTHu6pI46a7w+1qIwR3F6yO9ehWR8qvfLaBefJNFxaVw=="],
|
"@ai-sdk/azure": ["@ai-sdk/azure@2.0.6", "", { "dependencies": { "@ai-sdk/openai": "2.0.6", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-c8JbroE7B9Pyx3D8jxEwXop++vQqeR58adVrzPY7m29b/jZY7bsn6SuMheUF324sHbNJGd1DplqibIUzOs2qbA=="],
|
||||||
|
|
||||||
"@ai-sdk/google": ["@ai-sdk/google@1.2.18", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-8B70+i+uB12Ae6Sn6B9Oc6W0W/XorGgc88Nx0pyUrcxFOdytHBaAVhTPqYsO3LLClfjYN8pQ9GMxd5cpGEnUcA=="],
|
"@ai-sdk/gateway": ["@ai-sdk/gateway@1.0.4", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-1roLdgMbFU3Nr4MC97/te7w6OqxsWBkDUkpbCcvxF3jz/ku91WVaJldn/PKU8feMKNyI5W9wnqhbjb1BqbExOQ=="],
|
||||||
|
|
||||||
"@ai-sdk/openai": ["@ai-sdk/openai@1.3.22", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-QwA+2EkG0QyjVR+7h6FE7iOu2ivNqAVMm9UJZkVxxTk5OIq5fFJDTEI/zICEMuHImTTXR2JjsL6EirJ28Jc4cw=="],
|
"@ai-sdk/google": ["@ai-sdk/google@2.0.3", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-rQkY4nQGZnQw6pfftqsGsXXAqkGnl1gYjSbvD8+zyrVg2KAYeJU0NxIaacLWCORNcW+yQ/cqTpK/Uku/SIdEqA=="],
|
||||||
|
|
||||||
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@0.2.14", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icjObfMCHKSIbywijaoLdZ1nSnuRnWgMEMLgwoxPJgxsUHMx0aVORnsLUid4SPtdhHI3X2masrt6iaEQLvOSFw=="],
|
"@ai-sdk/openai": ["@ai-sdk/openai@2.0.6", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-YmnhiyqllxnGo0Jo23jfi7NCOK+8BhUPPV/Cm/8MGFgI9oX/s6xGIp6KqSK2GFlAz2vxyy+aAlpt0cv/a0SxYQ=="],
|
||||||
|
|
||||||
"@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
|
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.3", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-SE49TeOGppPtP4GTOXAl7WOhXXx42/VKpei3pNozjrD8NTkLX29Mib5t06VB5leaMjHNmyJMLECiWbWQV5W1ug=="],
|
||||||
|
|
||||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
|
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
|
||||||
|
|
||||||
"@ai-sdk/react": ["@ai-sdk/react@1.2.12", "", { "dependencies": { "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/ui-utils": "1.2.11", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["zod"] }, "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g=="],
|
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.1", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-/iP1sKc6UdJgGH98OCly7sWJKv+J9G47PnTjIj40IJMUQKwDrUMyf7zOOfRtPwSuNifYhSoJQ4s1WltI65gJ/g=="],
|
||||||
|
|
||||||
"@ai-sdk/ui-utils": ["@ai-sdk/ui-utils@1.2.11", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w=="],
|
"@ai-sdk/xai": ["@ai-sdk/xai@2.0.3", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.3", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-9Hsim5y4KJa+6DQBvoKcCp6ooWvfdbn1quOCEgSAnni6OJBL6QuImOcDmRr/YE6hrYo2RgLF56MtI2OyQqbh5w=="],
|
||||||
|
|
||||||
"@ai-sdk/xai": ["@ai-sdk/xai@1.2.16", "", { "dependencies": { "@ai-sdk/openai-compatible": "0.2.14", "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-UOZT8td9PWwMi2dF9a0U44t/Oltmf6QmIJdSvrOcLG4mvpRc1UJn6YJaR0HtXs3YnW6SvY1zRdIDrW4GFpv4NA=="],
|
|
||||||
|
|
||||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.2.14", "", { "dependencies": { "bun-types": "1.2.14" } }, "sha512-VsFZKs8oKHzI7zwvECiAJ5oSorWndIWEVhfbYqZd4HI/45kzW7PN2Rr5biAzvGvRuNmYLSANY+H59ubHq8xw7Q=="],
|
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
||||||
|
|
||||||
"@types/diff-match-patch": ["@types/diff-match-patch@1.0.36", "", {}, "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg=="],
|
"@types/bun": ["@types/bun@1.2.14", "", { "dependencies": { "bun-types": "1.2.14" } }, "sha512-VsFZKs8oKHzI7zwvECiAJ5oSorWndIWEVhfbYqZd4HI/45kzW7PN2Rr5biAzvGvRuNmYLSANY+H59ubHq8xw7Q=="],
|
||||||
|
|
||||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@22.15.21", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ=="],
|
"@types/node": ["@types/node@22.15.21", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ=="],
|
||||||
|
|
||||||
"ai": ["ai@4.3.16", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/react": "1.2.12", "@ai-sdk/ui-utils": "1.2.11", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g=="],
|
"ai": ["ai@5.0.8", "", { "dependencies": { "@ai-sdk/gateway": "1.0.4", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.1", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-qbnhj046UvG30V1S5WhjBn+RBGEAmi8PSZWqMhRsE3EPxvO5BcePXTZFA23e9MYyWS9zr4Vm8Mv3wQXwLmtIBw=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.2.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA=="],
|
"bun-types": ["bun-types@1.2.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA=="],
|
||||||
|
|
||||||
"chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
"eventsource-parser": ["eventsource-parser@3.0.3", "", {}, "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA=="],
|
||||||
|
|
||||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
|
||||||
|
|
||||||
"diff-match-patch": ["diff-match-patch@1.0.5", "", {}, "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="],
|
|
||||||
|
|
||||||
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
||||||
|
|
||||||
"jsondiffpatch": ["jsondiffpatch@0.6.0", "", { "dependencies": { "@types/diff-match-patch": "^1.0.36", "chalk": "^5.3.0", "diff-match-patch": "^1.0.5" }, "bin": { "jsondiffpatch": "bin/jsondiffpatch.js" } }, "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ=="],
|
|
||||||
|
|
||||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
|
||||||
|
|
||||||
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
|
|
||||||
|
|
||||||
"secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="],
|
|
||||||
|
|
||||||
"swr": ["swr@2.3.3", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A=="],
|
|
||||||
|
|
||||||
"throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="],
|
|
||||||
|
|
||||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||||
|
|
||||||
"use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="],
|
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
|
|
||||||
"zod": ["zod@3.25.30", "", {}, "sha512-VolhdEtu6TJr/fzGuHA/SZ5ixvXqA6ADOG9VRcQ3rdOKmF5hkmcJbyaQjUH5BgmpA9gej++zYRX7zjSmdReIwA=="],
|
|
||||||
|
|
||||||
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
|
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
package.json
17
package.json
|
|
@ -14,15 +14,16 @@
|
||||||
"anyclaude": "./dist/main.js"
|
"anyclaude": "./dist/main.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ai-sdk/anthropic": "^1.2.12",
|
"@ai-sdk/anthropic": "^2.0.1",
|
||||||
"@ai-sdk/azure": "^1.3.23",
|
"@ai-sdk/azure": "^2.0.6",
|
||||||
"@ai-sdk/google": "^1.2.18",
|
"@ai-sdk/google": "^2.0.3",
|
||||||
"@ai-sdk/openai": "^1.3.22",
|
"@ai-sdk/openai": "^2.0.6",
|
||||||
"@ai-sdk/xai": "^1.2.16",
|
"@ai-sdk/xai": "^2.0.3",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/json-schema": "^7.0.15",
|
"@types/json-schema": "^7.0.15",
|
||||||
"ai": "^4.3.16",
|
"ai": "^5.0.8",
|
||||||
"json-schema": "^0.4.0"
|
"json-schema": "^0.4.0",
|
||||||
|
"zod": "3.25.76"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
|
@ -30,6 +31,6 @@
|
||||||
"description": "Run Claude Code with OpenAI, Google, xAI, and others.",
|
"description": "Run Claude Code with OpenAI, Google, xAI, and others.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "bun build --target node --outfile dist/main.js ./src/main.ts --format cjs && sed -i '0,/^/s//#!\\/usr\\/bin\\/env node\\n/' ./dist/main.js"
|
"build": "bun build --target node --outfile dist/main.js ./src/main.ts --format cjs --external ai --external @ai-sdk/* --external zod && node -e \"const fs=require('fs');const p='dist/main.js';fs.writeFileSync(p,'#!/usr/bin/env node\\n'+fs.readFileSync(p,'utf8'))\" && chmod +x dist/main.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,8 @@ export interface AnthropicTextContent {
|
||||||
export interface AnthropicThinkingContent {
|
export interface AnthropicThinkingContent {
|
||||||
type: "thinking";
|
type: "thinking";
|
||||||
thinking: string;
|
thinking: string;
|
||||||
signature: string;
|
// signature is optional; not all providers expose it
|
||||||
|
signature?: string;
|
||||||
cache_control: AnthropicCacheControl | undefined;
|
cache_control: AnthropicCacheControl | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,14 +52,14 @@ export interface AnthropicRedactedThinkingContent {
|
||||||
|
|
||||||
type AnthropicContentSource =
|
type AnthropicContentSource =
|
||||||
| {
|
| {
|
||||||
type: "base64";
|
type: "base64";
|
||||||
media_type: string;
|
media_type: string;
|
||||||
data: string;
|
data: string;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "url";
|
type: "url";
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface AnthropicImageContent {
|
export interface AnthropicImageContent {
|
||||||
type: "image";
|
type: "image";
|
||||||
|
|
@ -90,25 +91,25 @@ export interface AnthropicToolResultContent {
|
||||||
|
|
||||||
export type AnthropicTool =
|
export type AnthropicTool =
|
||||||
| {
|
| {
|
||||||
name: string;
|
name: string;
|
||||||
description: string | undefined;
|
description: string | undefined;
|
||||||
input_schema: JSONSchema7;
|
input_schema: JSONSchema7;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
name: string;
|
name: string;
|
||||||
type: "computer_20250124" | "computer_20241022";
|
type: "computer_20250124" | "computer_20241022";
|
||||||
display_width_px: number;
|
display_width_px: number;
|
||||||
display_height_px: number;
|
display_height_px: number;
|
||||||
display_number: number;
|
display_number: number;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
name: string;
|
name: string;
|
||||||
type: "text_editor_20250124" | "text_editor_20241022";
|
type: "text_editor_20250124" | "text_editor_20241022";
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
name: string;
|
name: string;
|
||||||
type: "bash_20250124" | "bash_20241022";
|
type: "bash_20250124" | "bash_20241022";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AnthropicToolChoice =
|
export type AnthropicToolChoice =
|
||||||
| { type: "auto" | "any" }
|
| { type: "auto" | "any" }
|
||||||
|
|
@ -121,65 +122,65 @@ export type AnthropicStreamUsage = {
|
||||||
|
|
||||||
export type AnthropicStreamChunk =
|
export type AnthropicStreamChunk =
|
||||||
| {
|
| {
|
||||||
type: "message_start";
|
type: "message_start";
|
||||||
message: AnthropicAssistantMessage & {
|
message: AnthropicAssistantMessage & {
|
||||||
id: string;
|
id: string;
|
||||||
model: string;
|
model: string;
|
||||||
stop_reason: string | null;
|
stop_reason: string | null;
|
||||||
stop_sequence: string | null;
|
stop_sequence: string | null;
|
||||||
usage: AnthropicStreamUsage;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "content_block_start";
|
|
||||||
index: number;
|
|
||||||
content_block:
|
|
||||||
| {
|
|
||||||
type: "text";
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "tool_use";
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
input: any;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "content_block_delta";
|
|
||||||
index: number;
|
|
||||||
delta:
|
|
||||||
| {
|
|
||||||
type: "text_delta";
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "input_json_delta";
|
|
||||||
partial_json: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "content_block_stop";
|
|
||||||
index: number;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "message_delta";
|
|
||||||
delta: {
|
|
||||||
stop_reason: string;
|
|
||||||
stop_sequence: string | null;
|
|
||||||
};
|
|
||||||
usage: AnthropicStreamUsage;
|
usage: AnthropicStreamUsage;
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "message_stop";
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "error";
|
|
||||||
error: {
|
|
||||||
type: "api_error";
|
|
||||||
message: string;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "content_block_start";
|
||||||
|
index: number;
|
||||||
|
content_block:
|
||||||
|
| {
|
||||||
|
type: "text";
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "tool_use";
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
input: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "content_block_delta";
|
||||||
|
index: number;
|
||||||
|
delta:
|
||||||
|
| {
|
||||||
|
type: "text_delta";
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "input_json_delta";
|
||||||
|
partial_json: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "content_block_stop";
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "message_delta";
|
||||||
|
delta: {
|
||||||
|
stop_reason: string;
|
||||||
|
stop_sequence: string | null;
|
||||||
|
};
|
||||||
|
usage: AnthropicStreamUsage;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "message_stop";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "error";
|
||||||
|
error: {
|
||||||
|
type: "api_error";
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type AnthropicMessagesRequest = {
|
export type AnthropicMessagesRequest = {
|
||||||
model: string;
|
model: string;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { ProviderV1 } from "@ai-sdk/provider";
|
import type { ProviderV2 } from "@ai-sdk/provider";
|
||||||
import { jsonSchema, streamText, type Tool } from "ai";
|
import { jsonSchema, streamText, type Tool } from "ai";
|
||||||
import * as http from "http";
|
import * as http from "http";
|
||||||
import * as https from "https";
|
import * as https from "https";
|
||||||
|
|
@ -13,7 +13,7 @@ import { convertToLanguageModelMessage } from "./convert-to-language-model-promp
|
||||||
import { providerizeSchema } from "./json-schema";
|
import { providerizeSchema } from "./json-schema";
|
||||||
|
|
||||||
export type CreateAnthropicProxyOptions = {
|
export type CreateAnthropicProxyOptions = {
|
||||||
providers: Record<string, ProviderV1>;
|
providers: Record<string, ProviderV2>;
|
||||||
port?: number;
|
port?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -121,7 +121,7 @@ export const createAnthropicProxy = ({
|
||||||
const tools = body.tools?.reduce((acc, tool) => {
|
const tools = body.tools?.reduce((acc, tool) => {
|
||||||
acc[tool.name] = {
|
acc[tool.name] = {
|
||||||
description: tool.name,
|
description: tool.name,
|
||||||
parameters: jsonSchema(
|
inputSchema: jsonSchema(
|
||||||
providerizeSchema(providerName, tool.input_schema)
|
providerizeSchema(providerName, tool.input_schema)
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
@ -133,7 +133,7 @@ export const createAnthropicProxy = ({
|
||||||
system,
|
system,
|
||||||
tools,
|
tools,
|
||||||
messages: coreMessages,
|
messages: coreMessages,
|
||||||
maxTokens: body.max_tokens,
|
maxOutputTokens: body.max_tokens,
|
||||||
temperature: body.temperature,
|
temperature: body.temperature,
|
||||||
|
|
||||||
onFinish: ({ response, usage, finishReason }) => {
|
onFinish: ({ response, usage, finishReason }) => {
|
||||||
|
|
@ -161,7 +161,7 @@ export const createAnthropicProxy = ({
|
||||||
|
|
||||||
res.writeHead(200, { "Content-Type": "application/json" }).end(
|
res.writeHead(200, { "Content-Type": "application/json" }).end(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
id: message.id,
|
id: "msg_" + Date.now(),
|
||||||
type: "message",
|
type: "message",
|
||||||
role: promptMessage.role,
|
role: promptMessage.role,
|
||||||
content: promptMessage.content,
|
content: promptMessage.content,
|
||||||
|
|
@ -169,8 +169,8 @@ export const createAnthropicProxy = ({
|
||||||
stop_reason: mapAnthropicStopReason(finishReason),
|
stop_reason: mapAnthropicStopReason(finishReason),
|
||||||
stop_sequence: null,
|
stop_sequence: null,
|
||||||
usage: {
|
usage: {
|
||||||
input_tokens: usage.promptTokens,
|
input_tokens: usage.inputTokens,
|
||||||
output_tokens: usage.completionTokens,
|
output_tokens: usage.outputTokens,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import {
|
import {
|
||||||
type LanguageModelV1CallWarning,
|
type LanguageModelV2CallWarning,
|
||||||
type LanguageModelV1Message,
|
type LanguageModelV2Message,
|
||||||
type LanguageModelV1Prompt,
|
type LanguageModelV2Prompt,
|
||||||
type LanguageModelV1ProviderMetadata,
|
type SharedV2ProviderOptions,
|
||||||
UnsupportedFunctionalityError,
|
UnsupportedFunctionalityError,
|
||||||
} from "@ai-sdk/provider";
|
} from "@ai-sdk/provider";
|
||||||
import { convertUint8ArrayToBase64 } from "@ai-sdk/provider-utils";
|
import { convertUint8ArrayToBase64 } from "@ai-sdk/provider-utils";
|
||||||
|
|
@ -12,18 +12,19 @@ import type {
|
||||||
AnthropicMessage,
|
AnthropicMessage,
|
||||||
AnthropicMessagesPrompt,
|
AnthropicMessagesPrompt,
|
||||||
AnthropicUserMessage,
|
AnthropicUserMessage,
|
||||||
|
AnthropicToolResultContent,
|
||||||
} from "./anthropic-api-types";
|
} from "./anthropic-api-types";
|
||||||
import type { CoreMessage, FilePart, TextPart, ToolCallPart } from "ai";
|
import type { ModelMessage, FilePart, TextPart, ToolCallPart } from "ai";
|
||||||
import type { ReasoningUIPart } from "@ai-sdk/ui-utils";
|
import type { ReasoningUIPart } from 'ai';
|
||||||
|
|
||||||
export function convertToAnthropicMessagesPrompt({
|
export function convertToAnthropicMessagesPrompt({
|
||||||
prompt,
|
prompt,
|
||||||
sendReasoning,
|
sendReasoning,
|
||||||
warnings,
|
warnings,
|
||||||
}: {
|
}: {
|
||||||
prompt: LanguageModelV1Prompt;
|
prompt: LanguageModelV2Prompt;
|
||||||
sendReasoning: boolean;
|
sendReasoning: boolean;
|
||||||
warnings: LanguageModelV1CallWarning[];
|
warnings: LanguageModelV2CallWarning[];
|
||||||
}): {
|
}): {
|
||||||
prompt: AnthropicMessagesPrompt;
|
prompt: AnthropicMessagesPrompt;
|
||||||
betas: Set<string>;
|
betas: Set<string>;
|
||||||
|
|
@ -35,9 +36,9 @@ export function convertToAnthropicMessagesPrompt({
|
||||||
const messages: AnthropicMessagesPrompt["messages"] = [];
|
const messages: AnthropicMessagesPrompt["messages"] = [];
|
||||||
|
|
||||||
function getCacheControl(
|
function getCacheControl(
|
||||||
providerMetadata: LanguageModelV1ProviderMetadata | undefined
|
providerOptions: SharedV2ProviderOptions | undefined
|
||||||
): AnthropicCacheControl | undefined {
|
): AnthropicCacheControl | undefined {
|
||||||
const anthropic = providerMetadata?.anthropic;
|
const anthropic = providerOptions?.anthropic;
|
||||||
|
|
||||||
// allow both cacheControl and cache_control:
|
// allow both cacheControl and cache_control:
|
||||||
const cacheControlValue =
|
const cacheControlValue =
|
||||||
|
|
@ -62,10 +63,10 @@ export function convertToAnthropicMessagesPrompt({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
system = block.messages.map(({ content, providerMetadata }) => ({
|
system = block.messages.map(({ content, providerOptions }) => ({
|
||||||
type: "text",
|
type: "text",
|
||||||
text: content,
|
text: content,
|
||||||
cache_control: getCacheControl(providerMetadata),
|
cache_control: getCacheControl(providerOptions),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -76,139 +77,119 @@ export function convertToAnthropicMessagesPrompt({
|
||||||
const anthropicContent: AnthropicUserMessage["content"] = [];
|
const anthropicContent: AnthropicUserMessage["content"] = [];
|
||||||
|
|
||||||
for (const message of block.messages) {
|
for (const message of block.messages) {
|
||||||
const { role, content } = message;
|
if (message.role === "user") {
|
||||||
switch (role) {
|
const content = message.content;
|
||||||
case "user": {
|
for (let j = 0; j < content.length; j++) {
|
||||||
for (let j = 0; j < content.length; j++) {
|
const part = content[j]!;
|
||||||
const part = content[j]!;
|
|
||||||
|
|
||||||
// cache control: first add cache control from part.
|
const isLastPart = j === content.length - 1;
|
||||||
// for the last part of a message,
|
const cacheControl =
|
||||||
// check also if the message has cache control.
|
getCacheControl(part.providerOptions) ??
|
||||||
const isLastPart = j === content.length - 1;
|
(isLastPart ? getCacheControl(message.providerOptions) : undefined);
|
||||||
|
|
||||||
const cacheControl =
|
|
||||||
getCacheControl(part.providerMetadata) ??
|
|
||||||
(isLastPart
|
|
||||||
? getCacheControl(message.providerMetadata)
|
|
||||||
: undefined);
|
|
||||||
|
|
||||||
switch (part.type) {
|
|
||||||
case "text": {
|
|
||||||
anthropicContent.push({
|
|
||||||
type: "text",
|
|
||||||
text: part.text,
|
|
||||||
cache_control: cacheControl,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "image": {
|
|
||||||
anthropicContent.push({
|
|
||||||
type: "image",
|
|
||||||
source:
|
|
||||||
part.image instanceof URL
|
|
||||||
? {
|
|
||||||
type: "url",
|
|
||||||
url: part.image.toString(),
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
type: "base64",
|
|
||||||
media_type: part.mimeType ?? "image/jpeg",
|
|
||||||
data: convertUint8ArrayToBase64(part.image),
|
|
||||||
},
|
|
||||||
cache_control: cacheControl,
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "file": {
|
|
||||||
if (part.mimeType !== "application/pdf") {
|
|
||||||
throw new UnsupportedFunctionalityError({
|
|
||||||
functionality: "Non-PDF files in user messages",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
betas.add("pdfs-2024-09-25");
|
|
||||||
|
|
||||||
anthropicContent.push({
|
|
||||||
type: "document",
|
|
||||||
source:
|
|
||||||
part.data instanceof URL
|
|
||||||
? {
|
|
||||||
type: "url",
|
|
||||||
url: part.data.toString(),
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
type: "base64",
|
|
||||||
media_type: "application/pdf",
|
|
||||||
data: part.data,
|
|
||||||
},
|
|
||||||
cache_control: cacheControl,
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "tool": {
|
|
||||||
for (let i = 0; i < content.length; i++) {
|
|
||||||
const part = content[i]!;
|
|
||||||
|
|
||||||
// cache control: first add cache control from part.
|
|
||||||
// for the last part of a message,
|
|
||||||
// check also if the message has cache control.
|
|
||||||
const isLastPart = i === content.length - 1;
|
|
||||||
|
|
||||||
const cacheControl =
|
|
||||||
getCacheControl(part.providerMetadata) ??
|
|
||||||
(isLastPart
|
|
||||||
? getCacheControl(message.providerMetadata)
|
|
||||||
: undefined);
|
|
||||||
|
|
||||||
const toolResultContent =
|
|
||||||
part.content != null
|
|
||||||
? part.content.map((part) => {
|
|
||||||
switch (part.type) {
|
|
||||||
case "text":
|
|
||||||
return {
|
|
||||||
type: "text" as const,
|
|
||||||
text: part.text,
|
|
||||||
cache_control: undefined,
|
|
||||||
};
|
|
||||||
case "image":
|
|
||||||
return {
|
|
||||||
type: "image" as const,
|
|
||||||
source: {
|
|
||||||
type: "base64" as const,
|
|
||||||
media_type: part.mimeType ?? "image/jpeg",
|
|
||||||
data: part.data,
|
|
||||||
},
|
|
||||||
cache_control: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})
|
|
||||||
: JSON.stringify(part.result);
|
|
||||||
|
|
||||||
|
if (part.type === "text") {
|
||||||
anthropicContent.push({
|
anthropicContent.push({
|
||||||
type: "tool_result",
|
type: "text",
|
||||||
tool_use_id: part.toolCallId,
|
text: part.text,
|
||||||
content: toolResultContent,
|
|
||||||
is_error: part.isError,
|
|
||||||
cache_control: cacheControl,
|
cache_control: cacheControl,
|
||||||
});
|
});
|
||||||
|
} else if (part.type === "file") {
|
||||||
|
const mediaType = part.mediaType;
|
||||||
|
if (mediaType === "application/pdf") {
|
||||||
|
betas.add("pdfs-2024-09-25");
|
||||||
|
anthropicContent.push({
|
||||||
|
type: "document",
|
||||||
|
source:
|
||||||
|
part.data instanceof URL
|
||||||
|
? { type: "url", url: part.data.toString() }
|
||||||
|
: {
|
||||||
|
type: "base64",
|
||||||
|
media_type: "application/pdf",
|
||||||
|
data: typeof part.data === "string"
|
||||||
|
? part.data
|
||||||
|
: convertUint8ArrayToBase64(part.data),
|
||||||
|
},
|
||||||
|
cache_control: cacheControl,
|
||||||
|
});
|
||||||
|
} else if (mediaType?.startsWith("image/")) {
|
||||||
|
anthropicContent.push({
|
||||||
|
type: "image",
|
||||||
|
source:
|
||||||
|
part.data instanceof URL
|
||||||
|
? { type: "url", url: part.data.toString() }
|
||||||
|
: {
|
||||||
|
type: "base64",
|
||||||
|
media_type: mediaType ?? "image/jpeg",
|
||||||
|
data: typeof part.data === "string"
|
||||||
|
? part.data
|
||||||
|
: convertUint8ArrayToBase64(part.data),
|
||||||
|
},
|
||||||
|
cache_control: cacheControl,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedFunctionalityError({
|
||||||
|
functionality: `Unsupported user file media type: ${mediaType}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (message.role === "tool") {
|
||||||
|
const content = message.content;
|
||||||
|
for (let i = 0; i < content.length; i++) {
|
||||||
|
const part = content[i]!;
|
||||||
|
|
||||||
|
const isLastPart = i === content.length - 1;
|
||||||
|
const cacheControl =
|
||||||
|
getCacheControl(part.providerOptions) ??
|
||||||
|
(isLastPart ? getCacheControl(message.providerOptions) : undefined);
|
||||||
|
|
||||||
|
// Map LanguageModelV2ToolResultPart.output to Anthropic tool_result content
|
||||||
|
let toolResultContent: AnthropicToolResultContent["content"];
|
||||||
|
let isError: boolean | undefined;
|
||||||
|
|
||||||
|
switch (part.output.type) {
|
||||||
|
case "text":
|
||||||
|
toolResultContent = part.output.value;
|
||||||
|
isError = false;
|
||||||
|
break;
|
||||||
|
case "json":
|
||||||
|
toolResultContent = JSON.stringify(part.output.value);
|
||||||
|
isError = false;
|
||||||
|
break;
|
||||||
|
case "error-text":
|
||||||
|
toolResultContent = part.output.value;
|
||||||
|
isError = true;
|
||||||
|
break;
|
||||||
|
case "error-json":
|
||||||
|
toolResultContent = JSON.stringify(part.output.value);
|
||||||
|
isError = true;
|
||||||
|
break;
|
||||||
|
case "content":
|
||||||
|
toolResultContent = part.output.value.map((c) =>
|
||||||
|
c.type === "text"
|
||||||
|
? { type: "text" as const, text: c.text, cache_control: undefined }
|
||||||
|
: c.mediaType === "application/pdf"
|
||||||
|
? { type: "text" as const, text: "[document content omitted]", cache_control: undefined }
|
||||||
|
: {
|
||||||
|
type: "image" as const,
|
||||||
|
source: { type: "base64" as const, media_type: c.mediaType, data: c.data },
|
||||||
|
cache_control: undefined,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
isError = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
anthropicContent.push({
|
||||||
}
|
type: "tool_result",
|
||||||
default: {
|
tool_use_id: part.toolCallId,
|
||||||
const _exhaustiveCheck: never = role;
|
content: toolResultContent,
|
||||||
throw new Error(`Unsupported role: ${_exhaustiveCheck}`);
|
is_error: isError,
|
||||||
|
cache_control: cacheControl,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unsupported role in user block`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,9 +215,9 @@ export function convertToAnthropicMessagesPrompt({
|
||||||
// for the last part of a message,
|
// for the last part of a message,
|
||||||
// check also if the message has cache control.
|
// check also if the message has cache control.
|
||||||
const cacheControl =
|
const cacheControl =
|
||||||
getCacheControl(part.providerMetadata) ??
|
getCacheControl(part.providerOptions) ??
|
||||||
(isLastContentPart
|
(isLastContentPart
|
||||||
? getCacheControl(message.providerMetadata)
|
? getCacheControl(message.providerOptions)
|
||||||
: undefined);
|
: undefined);
|
||||||
|
|
||||||
switch (part.type) {
|
switch (part.type) {
|
||||||
|
|
@ -261,7 +242,6 @@ export function convertToAnthropicMessagesPrompt({
|
||||||
anthropicContent.push({
|
anthropicContent.push({
|
||||||
type: "thinking",
|
type: "thinking",
|
||||||
thinking: part.text,
|
thinking: part.text,
|
||||||
signature: part.signature!,
|
|
||||||
cache_control: cacheControl,
|
cache_control: cacheControl,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -274,21 +254,12 @@ export function convertToAnthropicMessagesPrompt({
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "redacted-reasoning": {
|
|
||||||
anthropicContent.push({
|
|
||||||
type: "redacted_thinking",
|
|
||||||
data: part.data,
|
|
||||||
cache_control: cacheControl,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "tool-call": {
|
case "tool-call": {
|
||||||
anthropicContent.push({
|
anthropicContent.push({
|
||||||
type: "tool_use",
|
type: "tool_use",
|
||||||
id: part.toolCallId,
|
id: part.toolCallId,
|
||||||
name: part.toolName,
|
name: part.toolName,
|
||||||
input: part.args,
|
input: part.input,
|
||||||
cache_control: cacheControl,
|
cache_control: cacheControl,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
@ -317,19 +288,19 @@ export function convertToAnthropicMessagesPrompt({
|
||||||
|
|
||||||
type SystemBlock = {
|
type SystemBlock = {
|
||||||
type: "system";
|
type: "system";
|
||||||
messages: Array<LanguageModelV1Message & { role: "system" }>;
|
messages: Array<LanguageModelV2Message & { role: "system" }>;
|
||||||
};
|
};
|
||||||
type AssistantBlock = {
|
type AssistantBlock = {
|
||||||
type: "assistant";
|
type: "assistant";
|
||||||
messages: Array<LanguageModelV1Message & { role: "assistant" }>;
|
messages: Array<LanguageModelV2Message & { role: "assistant" }>;
|
||||||
};
|
};
|
||||||
type UserBlock = {
|
type UserBlock = {
|
||||||
type: "user";
|
type: "user";
|
||||||
messages: Array<LanguageModelV1Message & { role: "user" | "tool" }>;
|
messages: Array<LanguageModelV2Message & { role: "user" | "tool" }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function groupIntoBlocks(
|
function groupIntoBlocks(
|
||||||
prompt: LanguageModelV1Prompt
|
prompt: LanguageModelV2Prompt
|
||||||
): Array<SystemBlock | AssistantBlock | UserBlock> {
|
): Array<SystemBlock | AssistantBlock | UserBlock> {
|
||||||
const blocks: Array<SystemBlock | AssistantBlock | UserBlock> = [];
|
const blocks: Array<SystemBlock | AssistantBlock | UserBlock> = [];
|
||||||
let currentBlock: SystemBlock | AssistantBlock | UserBlock | undefined =
|
let currentBlock: SystemBlock | AssistantBlock | UserBlock | undefined =
|
||||||
|
|
@ -387,7 +358,7 @@ function groupIntoBlocks(
|
||||||
export function convertFromAnthropicMessages(
|
export function convertFromAnthropicMessages(
|
||||||
messages: ReadonlyArray<AnthropicMessage>
|
messages: ReadonlyArray<AnthropicMessage>
|
||||||
) {
|
) {
|
||||||
const result: CoreMessage[] = [];
|
const result: ModelMessage[] = [];
|
||||||
let toolCalls: Record<string, ToolCallPart> = {};
|
let toolCalls: Record<string, ToolCallPart> = {};
|
||||||
|
|
||||||
for (const message of messages) {
|
for (const message of messages) {
|
||||||
|
|
@ -411,16 +382,16 @@ export function convertFromAnthropicMessages(
|
||||||
case "tool_use": {
|
case "tool_use": {
|
||||||
messageContent.push({
|
messageContent.push({
|
||||||
type: "tool-call",
|
type: "tool-call",
|
||||||
args: content.input,
|
input: content.input,
|
||||||
toolCallId: content.id,
|
toolCallId: content.id,
|
||||||
toolName: content.name,
|
toolName: content.name,
|
||||||
});
|
});
|
||||||
toolCalls[content.id] = {
|
toolCalls[content.id] = {
|
||||||
type: "tool-call",
|
type: "tool-call",
|
||||||
args: content.input,
|
input: content.input,
|
||||||
toolCallId: content.id,
|
toolCallId: content.id,
|
||||||
toolName: content.name,
|
toolName: content.name,
|
||||||
};
|
} as ToolCallPart;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "tool_result": {
|
case "tool_result": {
|
||||||
|
|
@ -428,11 +399,41 @@ export function convertFromAnthropicMessages(
|
||||||
if (!toolCall) {
|
if (!toolCall) {
|
||||||
throw new Error("Tool call not found");
|
throw new Error("Tool call not found");
|
||||||
}
|
}
|
||||||
|
// Map Anthropic tool_result content to LanguageModelV2ToolResultOutput
|
||||||
|
const output = ((): any => {
|
||||||
|
const c = content.content;
|
||||||
|
if (typeof c === "string") {
|
||||||
|
return { type: "text", value: c } as const;
|
||||||
|
}
|
||||||
|
// array of text/image contents
|
||||||
|
const mapped = c.map((p) => {
|
||||||
|
if (p.type === "text") {
|
||||||
|
return { type: "text" as const, text: p.text };
|
||||||
|
}
|
||||||
|
// image content: prefer base64 source
|
||||||
|
if (p.type === "image") {
|
||||||
|
if (p.source.type === "base64") {
|
||||||
|
return {
|
||||||
|
type: "media" as const,
|
||||||
|
data: p.source.data,
|
||||||
|
mediaType: p.source.media_type,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// URL fallback: encode as text with URL
|
||||||
|
return { type: "text" as const, text: p.source.url };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// default to text
|
||||||
|
return { type: "text" as const, text: "" };
|
||||||
|
});
|
||||||
|
return { type: "content", value: mapped } as const;
|
||||||
|
})();
|
||||||
|
|
||||||
result.push({
|
result.push({
|
||||||
role: "tool",
|
role: "tool",
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
result: content.content,
|
output,
|
||||||
toolCallId: content.tool_use_id,
|
toolCallId: content.tool_use_id,
|
||||||
toolName: toolCall.toolName,
|
toolName: toolCall.toolName,
|
||||||
type: "tool-result",
|
type: "tool-result",
|
||||||
|
|
@ -454,7 +455,7 @@ export function convertFromAnthropicMessages(
|
||||||
result.push({
|
result.push({
|
||||||
role: message.role,
|
role: message.role,
|
||||||
content: messageContent,
|
content: messageContent,
|
||||||
} as CoreMessage);
|
} as ModelMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -8,32 +8,30 @@ import {
|
||||||
export function convertToAnthropicStream(
|
export function convertToAnthropicStream(
|
||||||
stream: ReadableStream<TextStreamPart<Record<string, Tool>>>
|
stream: ReadableStream<TextStreamPart<Record<string, Tool>>>
|
||||||
): ReadableStream<AnthropicStreamChunk> {
|
): ReadableStream<AnthropicStreamChunk> {
|
||||||
|
let index = 0; // content block index within the current message
|
||||||
|
|
||||||
const transform = new TransformStream<
|
const transform = new TransformStream<
|
||||||
TextStreamPart<Record<string, Tool>>,
|
TextStreamPart<Record<string, Tool>>,
|
||||||
AnthropicStreamChunk
|
AnthropicStreamChunk
|
||||||
>({
|
>({
|
||||||
transform(chunk, controller) {
|
transform(chunk, controller) {
|
||||||
let index = 0;
|
|
||||||
|
|
||||||
switch (chunk.type) {
|
switch (chunk.type) {
|
||||||
case "step-start":
|
case "start-step": {
|
||||||
controller.enqueue({
|
controller.enqueue({
|
||||||
type: "message_start",
|
type: "message_start",
|
||||||
message: {
|
message: {
|
||||||
id: chunk.messageId,
|
id: "msg_" + Date.now(),
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
content: [],
|
content: [],
|
||||||
model: "claude-4-sonnet-20250514",
|
model: "claude-4-sonnet-20250514",
|
||||||
stop_reason: null,
|
stop_reason: null,
|
||||||
stop_sequence: null,
|
stop_sequence: null,
|
||||||
usage: {
|
usage: { input_tokens: 0, output_tokens: 0 },
|
||||||
input_tokens: 0,
|
|
||||||
output_tokens: 0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "step-finish":
|
}
|
||||||
|
case "finish-step": {
|
||||||
controller.enqueue({
|
controller.enqueue({
|
||||||
type: "message_delta",
|
type: "message_delta",
|
||||||
delta: {
|
delta: {
|
||||||
|
|
@ -41,63 +39,81 @@ export function convertToAnthropicStream(
|
||||||
stop_sequence: null,
|
stop_sequence: null,
|
||||||
},
|
},
|
||||||
usage: {
|
usage: {
|
||||||
input_tokens: chunk.usage.promptTokens,
|
input_tokens: chunk.usage.inputTokens ?? 0,
|
||||||
output_tokens: chunk.usage.completionTokens,
|
output_tokens: chunk.usage.outputTokens ?? 0,
|
||||||
},
|
|
||||||
});
|
|
||||||
index++;
|
|
||||||
break;
|
|
||||||
case "finish":
|
|
||||||
controller.enqueue({
|
|
||||||
type: "message_stop",
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "text-delta":
|
|
||||||
controller.enqueue({
|
|
||||||
type: "content_block_delta",
|
|
||||||
index: index,
|
|
||||||
delta: {
|
|
||||||
type: "text_delta",
|
|
||||||
text: chunk.textDelta,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "tool-call-streaming-start":
|
}
|
||||||
|
case "finish": {
|
||||||
|
controller.enqueue({ type: "message_stop" });
|
||||||
|
// reset index for next message
|
||||||
|
index = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "text-start": {
|
||||||
controller.enqueue({
|
controller.enqueue({
|
||||||
type: "content_block_start",
|
type: "content_block_start",
|
||||||
index: index,
|
index,
|
||||||
|
content_block: { type: "text", text: "" },
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "text-delta": {
|
||||||
|
controller.enqueue({
|
||||||
|
type: "content_block_delta",
|
||||||
|
index,
|
||||||
|
delta: { type: "text_delta", text: chunk.text },
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "text-end": {
|
||||||
|
controller.enqueue({ type: "content_block_stop", index });
|
||||||
|
index += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "tool-input-start": {
|
||||||
|
controller.enqueue({
|
||||||
|
type: "content_block_start",
|
||||||
|
index,
|
||||||
content_block: {
|
content_block: {
|
||||||
type: "tool_use",
|
type: "tool_use",
|
||||||
id: chunk.toolCallId,
|
id: chunk.id,
|
||||||
name: chunk.toolName,
|
name: chunk.toolName,
|
||||||
input: {},
|
input: {},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "tool-call-delta":
|
}
|
||||||
|
case "tool-input-delta": {
|
||||||
controller.enqueue({
|
controller.enqueue({
|
||||||
type: "content_block_delta",
|
type: "content_block_delta",
|
||||||
index: index,
|
index,
|
||||||
delta: {
|
delta: { type: "input_json_delta", partial_json: chunk.delta },
|
||||||
type: "input_json_delta",
|
|
||||||
partial_json: chunk.argsTextDelta,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "tool-call":
|
}
|
||||||
|
case "tool-input-end": {
|
||||||
|
controller.enqueue({ type: "content_block_stop", index });
|
||||||
|
index += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "tool-call": {
|
||||||
controller.enqueue({
|
controller.enqueue({
|
||||||
type: "content_block_start",
|
type: "content_block_start",
|
||||||
index: index,
|
index,
|
||||||
content_block: {
|
content_block: {
|
||||||
type: "tool_use",
|
type: "tool_use",
|
||||||
id: chunk.toolCallId,
|
id: chunk.toolCallId,
|
||||||
name: chunk.toolName,
|
name: chunk.toolName,
|
||||||
input: chunk.args,
|
input: (chunk as any).input,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
index++;
|
controller.enqueue({ type: "content_block_stop", index });
|
||||||
|
index += 1;
|
||||||
break;
|
break;
|
||||||
case "error":
|
}
|
||||||
|
case "error": {
|
||||||
controller.enqueue({
|
controller.enqueue({
|
||||||
type: "error",
|
type: "error",
|
||||||
error: {
|
error: {
|
||||||
|
|
@ -105,17 +121,29 @@ export function convertToAnthropicStream(
|
||||||
message:
|
message:
|
||||||
chunk.error instanceof Error
|
chunk.error instanceof Error
|
||||||
? chunk.error.message
|
? chunk.error.message
|
||||||
: chunk.error as string,
|
: (chunk.error as string),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
}
|
||||||
|
case "start":
|
||||||
|
case "abort":
|
||||||
|
case "raw":
|
||||||
|
case "source":
|
||||||
|
case "file":
|
||||||
|
case "reasoning-start":
|
||||||
|
case "reasoning-delta":
|
||||||
|
case "reasoning-end":
|
||||||
|
// ignore for Anthropic stream mapping
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
controller.error(new Error(`Unhandled chunk type: ${chunk.type}`));
|
controller.error(new Error(`Unhandled chunk type: ${chunk.type}`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
stream.pipeTo(transform.writable).catch((err) => {
|
stream.pipeTo(transform.writable).catch(() => {
|
||||||
console.log("WE GOT AN ERROR");
|
// swallow propagation; error already forwarded via 'error' chunk
|
||||||
});
|
});
|
||||||
return transform.readable;
|
return transform.readable;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import type {
|
import type {
|
||||||
LanguageModelV1FilePart,
|
LanguageModelV2FilePart,
|
||||||
LanguageModelV1ImagePart,
|
LanguageModelV2Message,
|
||||||
LanguageModelV1Message,
|
LanguageModelV2TextPart,
|
||||||
LanguageModelV1TextPart,
|
LanguageModelV2ToolCallPart,
|
||||||
|
LanguageModelV2ReasoningPart,
|
||||||
} from "@ai-sdk/provider";
|
} from "@ai-sdk/provider";
|
||||||
import {
|
import {
|
||||||
InvalidMessageRoleError,
|
InvalidMessageRoleError,
|
||||||
type CoreMessage,
|
type ModelMessage,
|
||||||
type DataContent,
|
type DataContent,
|
||||||
type FilePart,
|
type FilePart,
|
||||||
type ImagePart,
|
type ImagePart,
|
||||||
|
|
@ -27,20 +28,19 @@ import { splitDataUrl } from "./split-data-url";
|
||||||
* available if the model does not support URLs, null otherwise.
|
* available if the model does not support URLs, null otherwise.
|
||||||
*/
|
*/
|
||||||
export function convertToLanguageModelMessage(
|
export function convertToLanguageModelMessage(
|
||||||
message: CoreMessage,
|
message: ModelMessage,
|
||||||
downloadedAssets: Record<
|
downloadedAssets: Record<
|
||||||
string,
|
string,
|
||||||
{ mimeType: string | undefined; data: Uint8Array }
|
{ mimeType: string | undefined; data: Uint8Array }
|
||||||
>
|
>
|
||||||
): LanguageModelV1Message {
|
): LanguageModelV2Message {
|
||||||
const role = message.role;
|
const role = message.role;
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case "system": {
|
case "system": {
|
||||||
return {
|
return {
|
||||||
role: "system",
|
role: "system",
|
||||||
content: message.content,
|
content: message.content,
|
||||||
providerMetadata:
|
providerOptions: message.providerOptions,
|
||||||
message.providerOptions ?? message.experimental_providerMetadata,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,8 +49,7 @@ export function convertToLanguageModelMessage(
|
||||||
return {
|
return {
|
||||||
role: "user",
|
role: "user",
|
||||||
content: [{ type: "text", text: message.content }],
|
content: [{ type: "text", text: message.content }],
|
||||||
providerMetadata:
|
providerOptions: message.providerOptions,
|
||||||
message.providerOptions ?? message.experimental_providerMetadata,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,8 +59,7 @@ export function convertToLanguageModelMessage(
|
||||||
.map((part) => convertPartToLanguageModelPart(part, downloadedAssets))
|
.map((part) => convertPartToLanguageModelPart(part, downloadedAssets))
|
||||||
// remove empty text parts:
|
// remove empty text parts:
|
||||||
.filter((part) => part.type !== "text" || part.text !== ""),
|
.filter((part) => part.type !== "text" || part.text !== ""),
|
||||||
providerMetadata:
|
providerOptions: message.providerOptions,
|
||||||
message.providerOptions ?? message.experimental_providerMetadata,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,70 +68,69 @@ export function convertToLanguageModelMessage(
|
||||||
return {
|
return {
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
content: [{ type: "text", text: message.content }],
|
content: [{ type: "text", text: message.content }],
|
||||||
providerMetadata:
|
providerOptions: message.providerOptions,
|
||||||
message.providerOptions ?? message.experimental_providerMetadata,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const assistantParts: Array<
|
||||||
|
| LanguageModelV2TextPart
|
||||||
|
| LanguageModelV2FilePart
|
||||||
|
| LanguageModelV2ReasoningPart
|
||||||
|
| LanguageModelV2ToolCallPart
|
||||||
|
> = [];
|
||||||
|
|
||||||
|
for (const part of message.content) {
|
||||||
|
if (part.type === "text" && part.text === "") continue;
|
||||||
|
const providerOptions = part.providerOptions;
|
||||||
|
switch (part.type) {
|
||||||
|
case "file":
|
||||||
|
assistantParts.push({
|
||||||
|
type: "file",
|
||||||
|
data:
|
||||||
|
part.data instanceof URL
|
||||||
|
? part.data
|
||||||
|
: convertDataContentToBase64String(part.data),
|
||||||
|
filename: part.filename,
|
||||||
|
mediaType: part.mediaType,
|
||||||
|
providerOptions,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "reasoning":
|
||||||
|
assistantParts.push({
|
||||||
|
type: "reasoning",
|
||||||
|
text: part.text,
|
||||||
|
providerOptions,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "text":
|
||||||
|
assistantParts.push({
|
||||||
|
type: "text",
|
||||||
|
text: part.text,
|
||||||
|
providerOptions,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "tool-call":
|
||||||
|
assistantParts.push({
|
||||||
|
type: "tool-call",
|
||||||
|
toolCallId: part.toolCallId,
|
||||||
|
toolName: part.toolName,
|
||||||
|
input: part.input,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "tool-result":
|
||||||
|
// ignore here; mapped in tool message case
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
const _never: never = part;
|
||||||
|
void _never;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
content: message.content
|
content: assistantParts,
|
||||||
.filter(
|
providerOptions: message.providerOptions,
|
||||||
// remove empty text parts:
|
|
||||||
(part) => part.type !== "text" || part.text !== ""
|
|
||||||
)
|
|
||||||
.map((part) => {
|
|
||||||
const providerOptions =
|
|
||||||
part.providerOptions ?? part.experimental_providerMetadata;
|
|
||||||
|
|
||||||
switch (part.type) {
|
|
||||||
case "file": {
|
|
||||||
return {
|
|
||||||
type: "file",
|
|
||||||
data:
|
|
||||||
part.data instanceof URL
|
|
||||||
? part.data
|
|
||||||
: convertDataContentToBase64String(part.data),
|
|
||||||
filename: part.filename,
|
|
||||||
mimeType: part.mimeType,
|
|
||||||
providerMetadata: providerOptions,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "reasoning": {
|
|
||||||
return {
|
|
||||||
type: "reasoning",
|
|
||||||
text: part.text,
|
|
||||||
signature: part.signature,
|
|
||||||
providerMetadata: providerOptions,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "redacted-reasoning": {
|
|
||||||
return {
|
|
||||||
type: "redacted-reasoning",
|
|
||||||
data: part.data,
|
|
||||||
providerMetadata: providerOptions,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "text": {
|
|
||||||
return {
|
|
||||||
type: "text" as const,
|
|
||||||
text: part.text,
|
|
||||||
providerMetadata: providerOptions,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "tool-call": {
|
|
||||||
return {
|
|
||||||
type: "tool-call" as const,
|
|
||||||
toolCallId: part.toolCallId,
|
|
||||||
toolName: part.toolName,
|
|
||||||
args: part.args,
|
|
||||||
providerMetadata: providerOptions,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
providerMetadata:
|
|
||||||
message.providerOptions ?? message.experimental_providerMetadata,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,14 +141,10 @@ export function convertToLanguageModelMessage(
|
||||||
type: "tool-result",
|
type: "tool-result",
|
||||||
toolCallId: part.toolCallId,
|
toolCallId: part.toolCallId,
|
||||||
toolName: part.toolName,
|
toolName: part.toolName,
|
||||||
result: part.result,
|
providerOptions: part.providerOptions,
|
||||||
content: part.experimental_content,
|
output: part.output,
|
||||||
isError: part.isError,
|
|
||||||
providerMetadata:
|
|
||||||
part.providerOptions ?? part.experimental_providerMetadata,
|
|
||||||
})),
|
})),
|
||||||
providerMetadata:
|
providerOptions: message.providerOptions,
|
||||||
message.providerOptions ?? message.experimental_providerMetadata,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,19 +170,17 @@ function convertPartToLanguageModelPart(
|
||||||
{ mimeType: string | undefined; data: Uint8Array }
|
{ mimeType: string | undefined; data: Uint8Array }
|
||||||
>
|
>
|
||||||
):
|
):
|
||||||
| LanguageModelV1TextPart
|
| LanguageModelV2TextPart
|
||||||
| LanguageModelV1ImagePart
|
| LanguageModelV2FilePart {
|
||||||
| LanguageModelV1FilePart {
|
|
||||||
if (part.type === "text") {
|
if (part.type === "text") {
|
||||||
return {
|
return {
|
||||||
type: "text",
|
type: "text",
|
||||||
text: part.text,
|
text: part.text,
|
||||||
providerMetadata:
|
providerOptions: part.providerOptions,
|
||||||
part.providerOptions ?? part.experimental_providerMetadata,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mimeType: string | undefined = part.mimeType;
|
let mediaType = part.mediaType;
|
||||||
let data: DataContent | URL;
|
let data: DataContent | URL;
|
||||||
let content: URL | ArrayBuffer | string;
|
let content: URL | ArrayBuffer | string;
|
||||||
let normalizedData: Uint8Array | URL;
|
let normalizedData: Uint8Array | URL;
|
||||||
|
|
@ -228,7 +219,7 @@ function convertPartToLanguageModelPart(
|
||||||
throw new Error(`Invalid data URL format in part ${type}`);
|
throw new Error(`Invalid data URL format in part ${type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
mimeType = dataUrlMimeType;
|
mediaType = dataUrlMimeType;
|
||||||
normalizedData = convertDataContentToUint8Array(base64Content);
|
normalizedData = convertDataContentToUint8Array(base64Content);
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
|
|
@ -239,7 +230,7 @@ function convertPartToLanguageModelPart(
|
||||||
const downloadedFile = downloadedAssets[content.toString()];
|
const downloadedFile = downloadedAssets[content.toString()];
|
||||||
if (downloadedFile) {
|
if (downloadedFile) {
|
||||||
normalizedData = downloadedFile.data;
|
normalizedData = downloadedFile.data;
|
||||||
mimeType ??= downloadedFile.mimeType;
|
mediaType ??= downloadedFile.mimeType;
|
||||||
} else {
|
} else {
|
||||||
normalizedData = content;
|
normalizedData = content;
|
||||||
}
|
}
|
||||||
|
|
@ -259,24 +250,23 @@ function convertPartToLanguageModelPart(
|
||||||
// When detection fails, use provided mimetype.
|
// When detection fails, use provided mimetype.
|
||||||
|
|
||||||
if (normalizedData instanceof Uint8Array) {
|
if (normalizedData instanceof Uint8Array) {
|
||||||
mimeType =
|
mediaType =
|
||||||
detectMimeType({
|
detectMimeType({
|
||||||
data: normalizedData,
|
data: normalizedData,
|
||||||
signatures: imageMimeTypeSignatures,
|
signatures: imageMimeTypeSignatures,
|
||||||
}) ?? mimeType;
|
}) ?? mediaType;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: "image",
|
type: "file",
|
||||||
image: normalizedData,
|
data: normalizedData,
|
||||||
mimeType,
|
mediaType: mediaType ?? "",
|
||||||
providerMetadata:
|
providerOptions: part.providerOptions,
|
||||||
part.providerOptions ?? part.experimental_providerMetadata,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "file": {
|
case "file": {
|
||||||
// We should have a mimeType at this point, if not, throw an error.
|
// We should have a mimeType at this point, if not, throw an error.
|
||||||
if (mimeType == null) {
|
if (mediaType == null) {
|
||||||
throw new Error(`Mime type is missing for file part`);
|
throw new Error(`Mime type is missing for file part`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,9 +277,8 @@ function convertPartToLanguageModelPart(
|
||||||
? convertDataContentToBase64String(normalizedData)
|
? convertDataContentToBase64String(normalizedData)
|
||||||
: normalizedData,
|
: normalizedData,
|
||||||
filename: part.filename,
|
filename: part.filename,
|
||||||
mimeType,
|
mediaType: mediaType,
|
||||||
providerMetadata:
|
providerOptions: part.providerOptions,
|
||||||
part.providerOptions ?? part.experimental_providerMetadata,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import {
|
||||||
convertUint8ArrayToBase64,
|
convertUint8ArrayToBase64,
|
||||||
} from "@ai-sdk/provider-utils";
|
} from "@ai-sdk/provider-utils";
|
||||||
import { InvalidDataContentError } from "./invalid-data-content-error";
|
import { InvalidDataContentError } from "./invalid-data-content-error";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Data content. Can either be a base64-encoded string, a Uint8Array, an ArrayBuffer, or a Buffer.
|
Data content. Can either be a base64-encoded string, a Uint8Array, an ArrayBuffer, or a Buffer.
|
||||||
|
|
@ -13,17 +12,7 @@ export type DataContent = string | Uint8Array | ArrayBuffer | Buffer;
|
||||||
/**
|
/**
|
||||||
@internal
|
@internal
|
||||||
*/
|
*/
|
||||||
export const dataContentSchema: z.ZodType<DataContent> = z.union([
|
// Note: validate via runtime guards in converter functions; no Zod schema to avoid bundler TS resolution issues.
|
||||||
z.string(),
|
|
||||||
z.instanceof(Uint8Array),
|
|
||||||
z.instanceof(ArrayBuffer),
|
|
||||||
z.custom(
|
|
||||||
// Buffer might not be available in some environments such as CloudFlare:
|
|
||||||
(value: unknown): value is Buffer =>
|
|
||||||
globalThis.Buffer?.isBuffer(value) ?? false,
|
|
||||||
{ message: "Must be a Buffer" }
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Converts data content to a base64-encoded string.
|
Converts data content to a base64-encoded string.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { AISDKError } from "@ai-sdk/provider";
|
import { AISDKError } from 'ai';
|
||||||
|
|
||||||
const name = "AI_InvalidDataContentError";
|
const name = "AI_InvalidDataContentError";
|
||||||
const marker = `vercel.ai.error.${name}`;
|
const marker = `vercel.ai.error.${name}`;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue