mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-15 17:08:21 -04:00
Compare commits
130 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1de1c51240 | |||
| 3da0dea8e7 | |||
| c7aa47c144 | |||
| 7be95bcd6a | |||
| 3f4fb3f9db | |||
| 6702ce0d3f | |||
| a9895a1e8a | |||
| 75c7ac6a2c | |||
| 0eb71d0fc7 | |||
| deee40c572 | |||
| 8b5655ed53 | |||
| b9525b5878 | |||
| 2b8d1998d6 | |||
| 391cfbcfe7 | |||
| fe0c74f4df | |||
| 6d32bc9cb0 | |||
| 925c2423de | |||
| 71cb419570 | |||
| edc93ceff1 | |||
| 86a468c4d8 | |||
| f05d2ab551 | |||
| bef6cfbffe | |||
| fe9a936867 | |||
| d5669ca934 | |||
| 04f0a771a3 | |||
| c50554d907 | |||
| 3f5ad8441f | |||
| cf6e5b3604 | |||
| cbcf191fdb | |||
| ba0bbdafaa | |||
| a288cb5a0c | |||
| 57ff57595a | |||
| 584fdefe6f | |||
| c310ef82f4 | |||
| 4e85a37590 | |||
| fe9b051d1a | |||
| 33f1b269e9 | |||
| 0d68b0bb20 | |||
| 4e56998d3c | |||
| d625bc86fc | |||
| 5f437a09b0 | |||
| 7d4496eafc | |||
| 1f2de535aa | |||
| 529d55b1c3 | |||
| deb5b144c3 | |||
| cd3cca0006 | |||
| 08a7080e11 | |||
| f2579c41b6 | |||
| c4830c147d | |||
| 22c23cb44c | |||
| 60ce33cde8 | |||
| 6ea8247e0f | |||
| 987242b3e8 | |||
| fafb04ed59 | |||
| 87d5b27668 | |||
| c7a7900ff2 | |||
| f2a4011371 | |||
| 4bc8faa01c | |||
| 5d5b33f195 | |||
| 3fc06accbd | |||
| 677526d72e | |||
| bce9c639d1 | |||
| 48adb9521c | |||
| 9b3a4655f1 | |||
| bd79e24842 | |||
| d67e035c18 | |||
| f8470404e3 | |||
| 651751405d | |||
| 44f7bb71c1 | |||
| b26d948b76 | |||
| 2180fb8e7b | |||
| 3db4458e6c | |||
| 42e2dde739 | |||
| a1b274e6f8 | |||
| 27e1692848 | |||
| 73567f3570 | |||
| 91238441a6 | |||
| faf964691b | |||
| 4a7f760d25 | |||
| f76e18201a | |||
| 331533deff | |||
| 309860558d | |||
| b4a4ef0b3c | |||
| e916b99742 | |||
| 88b8883122 | |||
| ac1b802820 | |||
| 5ee4c1082a | |||
| 8a70d70006 | |||
| 91b4634363 | |||
| 4f60bde502 | |||
| 0d3b6d430e | |||
| a7cf21e157 | |||
| e4a16830f1 | |||
| 103f764624 | |||
| 7a7075d86f | |||
| c764732aea | |||
| 72af084dc1 | |||
| 65a42fd549 | |||
| ad8e6b1fb6 | |||
| 041cda905d | |||
| 2ad6c42143 | |||
| 60c7f847c1 | |||
| 7ab8a08efa | |||
| 7388e69b5c | |||
| 56c0658e6f | |||
| 06a2592836 | |||
| 4829308f2d | |||
| eec6cd5234 | |||
| fc37ae4452 | |||
| 5fcef6773c | |||
| a5b28c2af2 | |||
| d01dfa57b7 | |||
| 198ca749fd | |||
| 75f9fd5208 | |||
| 282f3f7eb2 | |||
| 4678bd1049 | |||
| 5fb0470b44 | |||
| 6bf92aa7de | |||
| 0a5530d0be | |||
| dc7e0f6d2e | |||
| b4be28d4c3 | |||
| 7624c4f7b3 | |||
| 720f062cd0 | |||
| b5177adb5b | |||
| a772767e9b | |||
| fd19fa89c4 | |||
| 0cfa7b39db | |||
| 5e2e0d6965 | |||
| f92d84746b | |||
| f2f5eb6f16 |
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@opencode-ai/cli": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Expose a TUI plugin slot at the top of the session view.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@opencode-ai/client": patch
|
||||||
|
"@opencode-ai/plugin": patch
|
||||||
|
"@opencode-ai/protocol": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Expose transient, read-only session generation through the HTTP API, generated clients, and V2 plugin session context.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@opencode-ai/cli": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Expose a TUI plugin slot above the session composer.
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
packages/core/migration/**/snapshot.json linguist-generated
|
packages/core/migration/**/snapshot.json linguist-generated
|
||||||
packages/core/src/database/migration.gen.ts linguist-generated
|
packages/core/src/database/migration.gen.ts linguist-generated
|
||||||
|
packages/core/src/**/*.txt text eol=lf
|
||||||
|
|||||||
@@ -121,6 +121,55 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
version: ${{ needs.version.outputs.version }}
|
version: ${{ needs.version.outputs.version }}
|
||||||
|
|
||||||
|
build-node-cli:
|
||||||
|
needs: version
|
||||||
|
if: github.repository == 'anomalyco/opencode'
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
settings:
|
||||||
|
- target: linux-arm64
|
||||||
|
host: blacksmith-4vcpu-ubuntu-2404-arm
|
||||||
|
- target: linux-x64
|
||||||
|
host: blacksmith-4vcpu-ubuntu-2404
|
||||||
|
- target: darwin-arm64
|
||||||
|
host: macos-26
|
||||||
|
- target: windows-arm64
|
||||||
|
host: blacksmith-4vcpu-windows-2025
|
||||||
|
- target: windows-x64
|
||||||
|
host: blacksmith-4vcpu-windows-2025
|
||||||
|
runs-on: ${{ matrix.settings.host }}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
|
|
||||||
|
- uses: ./.github/actions/setup-bun
|
||||||
|
with:
|
||||||
|
install-flags: --os=* --cpu=*
|
||||||
|
|
||||||
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
node-version: "26.4.0"
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: bun packages/cli/script/build-node.ts --target=${{ matrix.settings.target }} --skip-install --outdir=dist/node
|
||||||
|
env:
|
||||||
|
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||||
|
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||||
|
|
||||||
|
- name: Verify service lifecycle
|
||||||
|
if: matrix.settings.target != 'windows-arm64'
|
||||||
|
working-directory: packages/cli
|
||||||
|
run: bun run script/service-smoke.ts --node
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: opencode-node-cli-${{ matrix.settings.target }}
|
||||||
|
path: packages/cli/dist/node/cli-node-*
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
sign-cli-windows:
|
sign-cli-windows:
|
||||||
needs:
|
needs:
|
||||||
- build-cli
|
- build-cli
|
||||||
@@ -413,6 +462,7 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- version
|
- version
|
||||||
- build-cli
|
- build-cli
|
||||||
|
- build-node-cli
|
||||||
- sign-cli-windows
|
- sign-cli-windows
|
||||||
- build-electron
|
- build-electron
|
||||||
if: always() && !failure() && !cancelled()
|
if: always() && !failure() && !cancelled()
|
||||||
@@ -461,6 +511,12 @@ jobs:
|
|||||||
name: opencode-preview-cli
|
name: opencode-preview-cli
|
||||||
path: packages/cli/dist
|
path: packages/cli/dist
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
|
with:
|
||||||
|
pattern: opencode-node-cli-*
|
||||||
|
path: packages/cli/dist/node
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
if: needs.version.outputs.release
|
if: needs.version.outputs.release
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -78,6 +78,20 @@ jobs:
|
|||||||
bun run script/build.ts --single --skip-install
|
bun run script/build.ts --single --skip-install
|
||||||
bun run script/service-smoke.ts
|
bun run script/service-smoke.ts
|
||||||
|
|
||||||
|
- name: Setup Node build runtime
|
||||||
|
if: always()
|
||||||
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
node-version: "26.4.0"
|
||||||
|
|
||||||
|
- name: Verify Node build
|
||||||
|
if: always()
|
||||||
|
timeout-minutes: 15
|
||||||
|
working-directory: packages/cli
|
||||||
|
run: |
|
||||||
|
bun run script/build-node.ts --single --skip-install --outdir=dist/node
|
||||||
|
bun run script/service-smoke.ts --node
|
||||||
|
|
||||||
- name: Check generated client
|
- name: Check generated client
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
working-directory: packages/client
|
working-directory: packages/client
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ node_modules
|
|||||||
playground
|
playground
|
||||||
tmp
|
tmp
|
||||||
dist
|
dist
|
||||||
|
dist-node
|
||||||
ts-dist
|
ts-dist
|
||||||
.turbo
|
.turbo
|
||||||
.typecheck-profiles
|
.typecheck-profiles
|
||||||
@@ -25,6 +26,7 @@ Session.vim
|
|||||||
a.out
|
a.out
|
||||||
target
|
target
|
||||||
.scripts
|
.scripts
|
||||||
|
.cache
|
||||||
.direnv/
|
.direnv/
|
||||||
|
|
||||||
# Local dev files
|
# Local dev files
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ Valid types are `feat`, `fix`, `docs`, `chore`, `refactor`, and `test`. Scopes a
|
|||||||
|
|
||||||
Examples: `fix(tui): simplify thinking toggle styling`, `docs: update contributing guide`, `chore(sdk): regenerate types`.
|
Examples: `fix(tui): simplify thinking toggle styling`, `docs: update contributing guide`, `chore(sdk): regenerate types`.
|
||||||
|
|
||||||
Never bypass Git hooks. Do not use `--no-verify` or otherwise disable, skip, or circumvent commit or push hooks. If a hook fails, fix the failure or stop and report it to the user.
|
|
||||||
|
|
||||||
## Style Guide
|
## Style Guide
|
||||||
|
|
||||||
### General Principles
|
### General Principles
|
||||||
|
|||||||
@@ -30,9 +30,30 @@
|
|||||||
"turbo": "2.10.2",
|
"turbo": "2.10.2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"packages/ai": {
|
||||||
|
"name": "@opencode-ai/ai",
|
||||||
|
"version": "1.17.20",
|
||||||
|
"dependencies": {
|
||||||
|
"@opencode-ai/schema": "workspace:*",
|
||||||
|
"@smithy/eventstream-codec": "4.2.14",
|
||||||
|
"@smithy/util-utf8": "4.2.2",
|
||||||
|
"aws4fetch": "1.0.20",
|
||||||
|
"effect": "catalog:",
|
||||||
|
"google-auth-library": "10.5.0",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@clack/prompts": "1.0.0-alpha.1",
|
||||||
|
"@effect/platform-node": "catalog:",
|
||||||
|
"@opencode-ai/http-recorder": "workspace:*",
|
||||||
|
"@tsconfig/bun": "catalog:",
|
||||||
|
"@types/bun": "catalog:",
|
||||||
|
"@typescript/native-preview": "catalog:",
|
||||||
|
"typescript": "catalog:",
|
||||||
|
},
|
||||||
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@corvu/drawer": "catalog:",
|
"@corvu/drawer": "catalog:",
|
||||||
"@dnd-kit/abstract": "0.5.0",
|
"@dnd-kit/abstract": "0.5.0",
|
||||||
@@ -98,7 +119,7 @@
|
|||||||
},
|
},
|
||||||
"packages/cli": {
|
"packages/cli": {
|
||||||
"name": "@opencode-ai/cli",
|
"name": "@opencode-ai/cli",
|
||||||
"version": "1.17.18",
|
"version": "1.18.3",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode2": "./bin/opencode2.cjs",
|
"opencode2": "./bin/opencode2.cjs",
|
||||||
},
|
},
|
||||||
@@ -111,27 +132,37 @@
|
|||||||
"@opencode-ai/server": "workspace:*",
|
"@opencode-ai/server": "workspace:*",
|
||||||
"@opencode-ai/tui": "workspace:*",
|
"@opencode-ai/tui": "workspace:*",
|
||||||
"@opentui/core": "catalog:",
|
"@opentui/core": "catalog:",
|
||||||
"@opentui/keymap": "catalog:",
|
|
||||||
"@opentui/solid": "catalog:",
|
"@opentui/solid": "catalog:",
|
||||||
"@parcel/watcher": "2.5.1",
|
"@parcel/watcher": "2.5.1",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"fuzzysort": "catalog:",
|
|
||||||
"immer": "11.1.4",
|
"immer": "11.1.4",
|
||||||
"jsonc-parser": "3.3.1",
|
"jsonc-parser": "3.3.1",
|
||||||
"open": "10.1.2",
|
"open": "10.1.2",
|
||||||
"opentui-spinner": "catalog:",
|
|
||||||
"semver": "catalog:",
|
"semver": "catalog:",
|
||||||
"solid-js": "catalog:",
|
"solid-js": "catalog:",
|
||||||
"strip-ansi": "7.1.2",
|
|
||||||
"uqr": "0.1.3",
|
"uqr": "0.1.3",
|
||||||
|
"ws": "8.21.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@lydell/node-pty-darwin-arm64": "1.2.0-beta.12",
|
||||||
|
"@lydell/node-pty-darwin-x64": "1.2.0-beta.12",
|
||||||
|
"@lydell/node-pty-linux-arm64": "1.2.0-beta.12",
|
||||||
|
"@lydell/node-pty-linux-x64": "1.2.0-beta.12",
|
||||||
|
"@lydell/node-pty-win32-arm64": "1.2.0-beta.12",
|
||||||
|
"@lydell/node-pty-win32-x64": "1.2.0-beta.12",
|
||||||
"@opencode-ai/protocol": "workspace:*",
|
"@opencode-ai/protocol": "workspace:*",
|
||||||
"@opencode-ai/script": "workspace:*",
|
"@opencode-ai/script": "workspace:*",
|
||||||
|
"@parcel/watcher-darwin-arm64": "2.5.1",
|
||||||
|
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
||||||
|
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
||||||
|
"@parcel/watcher-win32-arm64": "2.5.1",
|
||||||
|
"@parcel/watcher-win32-x64": "2.5.1",
|
||||||
"@tsconfig/bun": "catalog:",
|
"@tsconfig/bun": "catalog:",
|
||||||
"@types/bun": "catalog:",
|
"@types/bun": "catalog:",
|
||||||
"@types/semver": "catalog:",
|
"@types/semver": "catalog:",
|
||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
|
"vite": "catalog:",
|
||||||
|
"vite-plugin-solid": "catalog:",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/client": {
|
"packages/client": {
|
||||||
@@ -150,7 +181,7 @@
|
|||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"effect": "4.0.0-beta.83",
|
"effect": "4.0.0-beta.98",
|
||||||
},
|
},
|
||||||
"optionalPeers": [
|
"optionalPeers": [
|
||||||
"effect",
|
"effect",
|
||||||
@@ -158,7 +189,7 @@
|
|||||||
},
|
},
|
||||||
"packages/codemode": {
|
"packages/codemode": {
|
||||||
"name": "@opencode-ai/codemode",
|
"name": "@opencode-ai/codemode",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "8.15.0",
|
"acorn": "8.15.0",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -168,12 +199,11 @@
|
|||||||
"@tsconfig/bun": "catalog:",
|
"@tsconfig/bun": "catalog:",
|
||||||
"@types/bun": "catalog:",
|
"@types/bun": "catalog:",
|
||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
"typescript": "catalog:",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -209,7 +239,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -236,7 +266,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "3.0.82",
|
"@ai-sdk/anthropic": "3.0.82",
|
||||||
"@ai-sdk/openai": "3.0.48",
|
"@ai-sdk/openai": "3.0.48",
|
||||||
@@ -258,7 +288,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -282,7 +312,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/support": {
|
"packages/console/support": {
|
||||||
"name": "@opencode-ai/console-support",
|
"name": "@opencode-ai/console-support",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@opencode-ai/console-core": "workspace:*",
|
"@opencode-ai/console-core": "workspace:*",
|
||||||
@@ -302,7 +332,7 @@
|
|||||||
},
|
},
|
||||||
"packages/core": {
|
"packages/core": {
|
||||||
"name": "@opencode-ai/core",
|
"name": "@opencode-ai/core",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -336,10 +366,10 @@
|
|||||||
"@modelcontextprotocol/sdk": "1.29.0",
|
"@modelcontextprotocol/sdk": "1.29.0",
|
||||||
"@npmcli/arborist": "9.4.0",
|
"@npmcli/arborist": "9.4.0",
|
||||||
"@npmcli/config": "10.8.1",
|
"@npmcli/config": "10.8.1",
|
||||||
|
"@opencode-ai/ai": "workspace:*",
|
||||||
"@opencode-ai/codemode": "workspace:*",
|
"@opencode-ai/codemode": "workspace:*",
|
||||||
"@opencode-ai/effect-drizzle-sqlite": "workspace:*",
|
"@opencode-ai/effect-drizzle-sqlite": "workspace:*",
|
||||||
"@opencode-ai/effect-sqlite-node": "workspace:*",
|
"@opencode-ai/effect-sqlite-node": "workspace:*",
|
||||||
"@opencode-ai/ai": "workspace:*",
|
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
"@opencode-ai/schema": "workspace:*",
|
"@opencode-ai/schema": "workspace:*",
|
||||||
"@openrouter/ai-sdk-provider": "2.9.0",
|
"@openrouter/ai-sdk-provider": "2.9.0",
|
||||||
@@ -367,6 +397,7 @@
|
|||||||
"mime-types": "3.0.2",
|
"mime-types": "3.0.2",
|
||||||
"minimatch": "10.2.5",
|
"minimatch": "10.2.5",
|
||||||
"npm-package-arg": "13.0.2",
|
"npm-package-arg": "13.0.2",
|
||||||
|
"resolve.exports": "catalog:",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"turndown": "7.2.0",
|
"turndown": "7.2.0",
|
||||||
"venice-ai-sdk-provider": "2.1.1",
|
"venice-ai-sdk-provider": "2.1.1",
|
||||||
@@ -398,7 +429,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@zip.js/zip.js": "2.7.62",
|
"@zip.js/zip.js": "2.7.62",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -458,7 +489,7 @@
|
|||||||
},
|
},
|
||||||
"packages/effect-drizzle-sqlite": {
|
"packages/effect-drizzle-sqlite": {
|
||||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"drizzle-orm": "catalog:",
|
"drizzle-orm": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -472,7 +503,7 @@
|
|||||||
},
|
},
|
||||||
"packages/effect-sqlite-node": {
|
"packages/effect-sqlite-node": {
|
||||||
"name": "@opencode-ai/effect-sqlite-node",
|
"name": "@opencode-ai/effect-sqlite-node",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -484,7 +515,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/standard-validator": "catalog:",
|
"@hono/standard-validator": "catalog:",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
@@ -516,7 +547,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -532,9 +563,9 @@
|
|||||||
},
|
},
|
||||||
"packages/http-recorder": {
|
"packages/http-recorder": {
|
||||||
"name": "@opencode-ai/http-recorder",
|
"name": "@opencode-ai/http-recorder",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/platform-node-shared": "4.0.0-beta.83",
|
"@effect/platform-node-shared": "4.0.0-beta.98",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
@@ -562,29 +593,9 @@
|
|||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/ai": {
|
|
||||||
"name": "@opencode-ai/ai",
|
|
||||||
"version": "1.17.20",
|
|
||||||
"dependencies": {
|
|
||||||
"@opencode-ai/schema": "workspace:*",
|
|
||||||
"@smithy/eventstream-codec": "4.2.14",
|
|
||||||
"@smithy/util-utf8": "4.2.2",
|
|
||||||
"aws4fetch": "1.0.20",
|
|
||||||
"effect": "catalog:",
|
|
||||||
"google-auth-library": "10.5.0",
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@clack/prompts": "1.0.0-alpha.1",
|
|
||||||
"@effect/platform-node": "catalog:",
|
|
||||||
"@opencode-ai/http-recorder": "workspace:*",
|
|
||||||
"@tsconfig/bun": "catalog:",
|
|
||||||
"@types/bun": "catalog:",
|
|
||||||
"@typescript/native-preview": "catalog:",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -621,10 +632,10 @@
|
|||||||
"@octokit/graphql": "9.0.2",
|
"@octokit/graphql": "9.0.2",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
"@openauthjs/openauth": "catalog:",
|
"@openauthjs/openauth": "catalog:",
|
||||||
|
"@opencode-ai/ai": "workspace:*",
|
||||||
"@opencode-ai/cli": "workspace:*",
|
"@opencode-ai/cli": "workspace:*",
|
||||||
"@opencode-ai/client": "workspace:*",
|
"@opencode-ai/client": "workspace:*",
|
||||||
"@opencode-ai/codemode": "workspace:*",
|
"@opencode-ai/codemode": "workspace:*",
|
||||||
"@opencode-ai/ai": "workspace:*",
|
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
"@opencode-ai/protocol": "workspace:*",
|
"@opencode-ai/protocol": "workspace:*",
|
||||||
"@opencode-ai/schema": "workspace:*",
|
"@opencode-ai/schema": "workspace:*",
|
||||||
@@ -717,12 +728,14 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "3.0.8",
|
"@ai-sdk/provider": "3.0.8",
|
||||||
|
"@opencode-ai/ai": "workspace:*",
|
||||||
"@opencode-ai/client": "workspace:*",
|
"@opencode-ai/client": "workspace:*",
|
||||||
"@opencode-ai/schema": "workspace:*",
|
"@opencode-ai/schema": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
|
"@standard-schema/spec": "^1.1.0",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -737,9 +750,9 @@
|
|||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@opentui/core": ">=0.4.3",
|
"@opentui/core": ">=0.4.5",
|
||||||
"@opentui/keymap": ">=0.4.3",
|
"@opentui/keymap": ">=0.4.5",
|
||||||
"@opentui/solid": ">=0.4.3",
|
"@opentui/solid": ">=0.4.5",
|
||||||
},
|
},
|
||||||
"optionalPeers": [
|
"optionalPeers": [
|
||||||
"@opentui/core",
|
"@opentui/core",
|
||||||
@@ -804,7 +817,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": "catalog:",
|
"cross-spawn": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -819,7 +832,7 @@
|
|||||||
},
|
},
|
||||||
"packages/server": {
|
"packages/server": {
|
||||||
"name": "@opencode-ai/server",
|
"name": "@opencode-ai/server",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
@@ -836,7 +849,7 @@
|
|||||||
},
|
},
|
||||||
"packages/session-ui": {
|
"packages/session-ui": {
|
||||||
"name": "@opencode-ai/session-ui",
|
"name": "@opencode-ai/session-ui",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
@@ -883,9 +896,13 @@
|
|||||||
"version": "1.17.13",
|
"version": "1.17.13",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/commit-mono": "5.2.5",
|
"@fontsource/commit-mono": "5.2.5",
|
||||||
|
"@fontsource/noto-sans-math": "5.2.5",
|
||||||
|
"@fontsource/noto-sans-symbols": "5.2.5",
|
||||||
|
"@fontsource/noto-sans-symbols-2": "5.2.5",
|
||||||
"@napi-rs/canvas": "1.0.2",
|
"@napi-rs/canvas": "1.0.2",
|
||||||
"@opencode-ai/core": "workspace:*",
|
|
||||||
"@opencode-ai/ai": "workspace:*",
|
"@opencode-ai/ai": "workspace:*",
|
||||||
|
"@opencode-ai/core": "workspace:*",
|
||||||
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
"@opentui/core": "catalog:",
|
"@opentui/core": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -897,7 +914,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -910,7 +927,7 @@
|
|||||||
},
|
},
|
||||||
"packages/stats/app": {
|
"packages/stats/app": {
|
||||||
"name": "@opencode-ai/stats-app",
|
"name": "@opencode-ai/stats-app",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
@@ -944,7 +961,7 @@
|
|||||||
},
|
},
|
||||||
"packages/stats/core": {
|
"packages/stats/core": {
|
||||||
"name": "@opencode-ai/stats-core",
|
"name": "@opencode-ai/stats-core",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-athena": "3.933.0",
|
"@aws-sdk/client-athena": "3.933.0",
|
||||||
"@planetscale/database": "1.19.0",
|
"@planetscale/database": "1.19.0",
|
||||||
@@ -963,7 +980,7 @@
|
|||||||
},
|
},
|
||||||
"packages/stats/server": {
|
"packages/stats/server": {
|
||||||
"name": "@opencode-ai/stats-server",
|
"name": "@opencode-ai/stats-server",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-firehose": "3.933.0",
|
"@aws-sdk/client-firehose": "3.933.0",
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
@@ -1004,11 +1021,12 @@
|
|||||||
},
|
},
|
||||||
"packages/tui": {
|
"packages/tui": {
|
||||||
"name": "@opencode-ai/tui",
|
"name": "@opencode-ai/tui",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/client": "workspace:*",
|
"@opencode-ai/client": "workspace:*",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
|
"@opencode-ai/schema": "workspace:*",
|
||||||
"@opencode-ai/simulation": "workspace:*",
|
"@opencode-ai/simulation": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opentui/core": "catalog:",
|
"@opentui/core": "catalog:",
|
||||||
@@ -1019,10 +1037,12 @@
|
|||||||
"diff": "catalog:",
|
"diff": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"fuzzysort": "catalog:",
|
"fuzzysort": "catalog:",
|
||||||
|
"get-east-asian-width": "catalog:",
|
||||||
"open": "10.1.2",
|
"open": "10.1.2",
|
||||||
"opentui-spinner": "catalog:",
|
"opentui-spinner": "catalog:",
|
||||||
"remeda": "catalog:",
|
"remeda": "catalog:",
|
||||||
"solid-js": "catalog:",
|
"solid-js": "catalog:",
|
||||||
|
"string-width": "catalog:",
|
||||||
"strip-ansi": "7.1.2",
|
"strip-ansi": "7.1.2",
|
||||||
"uqr": "0.1.3",
|
"uqr": "0.1.3",
|
||||||
},
|
},
|
||||||
@@ -1034,7 +1054,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
@@ -1085,7 +1105,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -1161,12 +1181,12 @@
|
|||||||
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
|
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
|
||||||
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
|
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
|
||||||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||||
"effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch",
|
|
||||||
"@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch",
|
"@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch",
|
||||||
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
|
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
|
||||||
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
|
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
|
||||||
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
||||||
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
|
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
|
||||||
|
"effect@4.0.0-beta.98": "patches/effect@4.0.0-beta.98.patch",
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@opentui/core": "catalog:",
|
"@opentui/core": "catalog:",
|
||||||
@@ -1178,9 +1198,9 @@
|
|||||||
"catalog": {
|
"catalog": {
|
||||||
"@cloudflare/workers-types": "4.20251008.0",
|
"@cloudflare/workers-types": "4.20251008.0",
|
||||||
"@corvu/drawer": "0.2.4",
|
"@corvu/drawer": "0.2.4",
|
||||||
"@effect/opentelemetry": "4.0.0-beta.83",
|
"@effect/opentelemetry": "4.0.0-beta.98",
|
||||||
"@effect/platform-node": "4.0.0-beta.83",
|
"@effect/platform-node": "4.0.0-beta.98",
|
||||||
"@effect/sql-sqlite-bun": "4.0.0-beta.83",
|
"@effect/sql-sqlite-bun": "4.0.0-beta.98",
|
||||||
"@hono/standard-validator": "0.2.0",
|
"@hono/standard-validator": "0.2.0",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
@@ -1188,9 +1208,9 @@
|
|||||||
"@npmcli/arborist": "9.4.0",
|
"@npmcli/arborist": "9.4.0",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||||
"@opentui/core": "0.4.3",
|
"@opentui/core": "0.4.5",
|
||||||
"@opentui/keymap": "0.4.3",
|
"@opentui/keymap": "0.4.5",
|
||||||
"@opentui/solid": "0.4.3",
|
"@opentui/solid": "0.4.5",
|
||||||
"@pierre/diffs": "1.2.10",
|
"@pierre/diffs": "1.2.10",
|
||||||
"@playwright/test": "1.59.1",
|
"@playwright/test": "1.59.1",
|
||||||
"@sentry/solid": "10.36.0",
|
"@sentry/solid": "10.36.0",
|
||||||
@@ -1216,8 +1236,9 @@
|
|||||||
"dompurify": "3.3.1",
|
"dompurify": "3.3.1",
|
||||||
"drizzle-kit": "1.0.0-rc.2",
|
"drizzle-kit": "1.0.0-rc.2",
|
||||||
"drizzle-orm": "1.0.0-rc.2",
|
"drizzle-orm": "1.0.0-rc.2",
|
||||||
"effect": "4.0.0-beta.83",
|
"effect": "4.0.0-beta.98",
|
||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
|
"get-east-asian-width": "1.6.0",
|
||||||
"hono": "4.10.7",
|
"hono": "4.10.7",
|
||||||
"hono-openapi": "1.1.2",
|
"hono-openapi": "1.1.2",
|
||||||
"luxon": "3.6.1",
|
"luxon": "3.6.1",
|
||||||
@@ -1226,11 +1247,13 @@
|
|||||||
"opentui-spinner": "0.0.7",
|
"opentui-spinner": "0.0.7",
|
||||||
"remeda": "2.26.0",
|
"remeda": "2.26.0",
|
||||||
"remend": "1.3.0",
|
"remend": "1.3.0",
|
||||||
|
"resolve.exports": "2.0.3",
|
||||||
"semver": "7.7.4",
|
"semver": "7.7.4",
|
||||||
"shiki": "4.2.0",
|
"shiki": "4.2.0",
|
||||||
"solid-js": "1.9.10",
|
"solid-js": "1.9.10",
|
||||||
"solid-list": "0.3.0",
|
"solid-list": "0.3.0",
|
||||||
"sst": "4.13.1",
|
"sst": "4.13.1",
|
||||||
|
"string-width": "7.2.0",
|
||||||
"tailwindcss": "4.1.11",
|
"tailwindcss": "4.1.11",
|
||||||
"typescript": "5.8.2",
|
"typescript": "5.8.2",
|
||||||
"ulid": "3.0.1",
|
"ulid": "3.0.1",
|
||||||
@@ -1617,13 +1640,13 @@
|
|||||||
|
|
||||||
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="],
|
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="],
|
||||||
|
|
||||||
"@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.83", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.83" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-cPfCfp/ghu0itbX6Dqjdr4N0rbjng5ON4sUpnLHV5JJySG8zZpWmuOZLWIrfrNKT2ctYR1BYmp1aYCgkItaJLw=="],
|
"@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.98", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.98" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-ITfK8xhcl+9GXOvPwzADWkOQ+dgUGZrJNefT3r2+uLFmzjyKRLtHzhLOl6lZaLSsf5io13+nmt8adfMRQPq+oA=="],
|
||||||
|
|
||||||
"@effect/platform-node": ["@effect/platform-node@4.0.0-beta.83", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.83", "mime": "^4.1.0", "undici": "^8.2.0" }, "peerDependencies": { "effect": "^4.0.0-beta.83", "ioredis": "^5.7.0" } }, "sha512-RmpVGu/+X/Bif3/g1Rzj8oFzTOknoVB3yHCa0b179vytPpKe+Kj9ZwKNcAnKWqHUDkbSPBq1Ca60mvOHr2/+LQ=="],
|
"@effect/platform-node": ["@effect/platform-node@4.0.0-beta.98", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.98", "mime": "^4.1.0", "undici": "^8.7.0" }, "peerDependencies": { "effect": "^4.0.0-beta.98", "ioredis": "^5.7.0" } }, "sha512-IQu1TiLXQEDSGkDBllyYjVadf+UqdjptryqX4mmktVTTbGDq7X4uVxe7cSgXuqZvyfG6kagTzwj2lfynxOaKQg=="],
|
||||||
|
|
||||||
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.83", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.20.0" }, "peerDependencies": { "effect": "^4.0.0-beta.83" } }, "sha512-+yr/+PJmKTgmJq1QOINSBPgLu7Cjc4CZcotBXnGjyDEizOmimFgTkN2B8PBJAKIKUWYWfobjXqC+58/VhhPKAw=="],
|
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.98", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.21.0" }, "peerDependencies": { "effect": "^4.0.0-beta.98" } }, "sha512-iySXaffnCJX1sNAIp79ghhIeui9E5qwUQyqd1VLPkB9UNO4vdpd9B5fTEXwe7S/GusL4jsk9vSvX38XJgRFG1w=="],
|
||||||
|
|
||||||
"@effect/sql-sqlite-bun": ["@effect/sql-sqlite-bun@4.0.0-beta.83", "", { "peerDependencies": { "effect": "^4.0.0-beta.83" } }, "sha512-6OaxLsWffxkh9pXYUSyj/AxjVb9URY2rG9U6atjxClWy30Jx77R9Pm3Rrc7cQ63kQurePavEw1bQbzQ/SILiQQ=="],
|
"@effect/sql-sqlite-bun": ["@effect/sql-sqlite-bun@4.0.0-beta.98", "", { "peerDependencies": { "effect": "^4.0.0-beta.98" } }, "sha512-cc41uLhYBqexdbTNu4dlui+31E8hcVLEapLySa0C8d60FmBY8IEAV/RD3oF+6pqPslKEZ9p1+XVLdDm0iflw5Q=="],
|
||||||
|
|
||||||
"@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="],
|
"@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="],
|
||||||
|
|
||||||
@@ -1773,6 +1796,12 @@
|
|||||||
|
|
||||||
"@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
|
"@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
|
||||||
|
|
||||||
|
"@fontsource/noto-sans-math": ["@fontsource/noto-sans-math@5.2.5", "", {}, "sha512-1bxEvVlF51Vfgpju32mRZzI/CHvsfqjXjI2+sAuEyHYvXABUAIyj+93sCO3QZIoMG5drWyrzgoCqRQRaL6wQ8Q=="],
|
||||||
|
|
||||||
|
"@fontsource/noto-sans-symbols": ["@fontsource/noto-sans-symbols@5.2.5", "", {}, "sha512-mxoIRstsmZpZFzd/SRWiD+l6T7TGhpgCrGs7TEnnuGSQIfjVMrQT9Zej2enh9pkfmPNAFyeaGJkHkszJ1hH++w=="],
|
||||||
|
|
||||||
|
"@fontsource/noto-sans-symbols-2": ["@fontsource/noto-sans-symbols-2@5.2.5", "", {}, "sha512-F4O9WLifwoZS1quNzY1ebjMNo2cQPe/UP68Dmud0ONi2lOxaR6xp6fFPO2gG17MI7DwAnfMyQFl64A2tAd28hg=="],
|
||||||
|
|
||||||
"@fuma-translate/react": ["@fuma-translate/react@1.0.2", "", { "peerDependencies": { "@types/react": "*", "react": "^19.2.0", "react-dom": "^19.2.0" }, "optionalPeers": ["@types/react"] }, "sha512-uOiOtBx3nRXR8Nu1GzBf1tApgF1FErDBTHxRIAQeyQdyOoZbrNRN6H4kDCWObY4qyGeGbHydG0DHzgeUgFDMIw=="],
|
"@fuma-translate/react": ["@fuma-translate/react@1.0.2", "", { "peerDependencies": { "@types/react": "*", "react": "^19.2.0", "react-dom": "^19.2.0" }, "optionalPeers": ["@types/react"] }, "sha512-uOiOtBx3nRXR8Nu1GzBf1tApgF1FErDBTHxRIAQeyQdyOoZbrNRN6H4kDCWObY4qyGeGbHydG0DHzgeUgFDMIw=="],
|
||||||
|
|
||||||
"@fumadocs/tailwind": ["@fumadocs/tailwind@0.1.0", "", { "peerDependencies": { "tailwindcss": "^4.0.0" }, "optionalPeers": ["tailwindcss"] }, "sha512-nF/DCAwOR21HZ4AkjIOv3Iqwyqywzb6pdyeMcoa+aZzirXj5ntvNZbe3jJ0v3ehhtrRfYYeXBezvjn8ZmV+fuQ=="],
|
"@fumadocs/tailwind": ["@fumadocs/tailwind@0.1.0", "", { "peerDependencies": { "tailwindcss": "^4.0.0" }, "optionalPeers": ["tailwindcss"] }, "sha512-nF/DCAwOR21HZ4AkjIOv3Iqwyqywzb6pdyeMcoa+aZzirXj5ntvNZbe3jJ0v3ehhtrRfYYeXBezvjn8ZmV+fuQ=="],
|
||||||
@@ -2167,6 +2196,8 @@
|
|||||||
|
|
||||||
"@openauthjs/openauth": ["@openauthjs/openauth@0.0.0-20250322224806", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-p5IWSRXvABcwocH2dNI0w8c1QJelIOFulwhKk+aLLFfUbs8u1pr7kQbYe8yCSM2+bcLHiwbogpUQc2ovrGwCuw=="],
|
"@openauthjs/openauth": ["@openauthjs/openauth@0.0.0-20250322224806", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-p5IWSRXvABcwocH2dNI0w8c1QJelIOFulwhKk+aLLFfUbs8u1pr7kQbYe8yCSM2+bcLHiwbogpUQc2ovrGwCuw=="],
|
||||||
|
|
||||||
|
"@opencode-ai/ai": ["@opencode-ai/ai@workspace:packages/ai"],
|
||||||
|
|
||||||
"@opencode-ai/app": ["@opencode-ai/app@workspace:packages/app"],
|
"@opencode-ai/app": ["@opencode-ai/app@workspace:packages/app"],
|
||||||
|
|
||||||
"@opencode-ai/cli": ["@opencode-ai/cli@workspace:packages/cli"],
|
"@opencode-ai/cli": ["@opencode-ai/cli@workspace:packages/cli"],
|
||||||
@@ -2205,8 +2236,6 @@
|
|||||||
|
|
||||||
"@opencode-ai/httpapi-codegen": ["@opencode-ai/httpapi-codegen@workspace:packages/httpapi-codegen"],
|
"@opencode-ai/httpapi-codegen": ["@opencode-ai/httpapi-codegen@workspace:packages/httpapi-codegen"],
|
||||||
|
|
||||||
"@opencode-ai/ai": ["@opencode-ai/ai@workspace:packages/ai"],
|
|
||||||
|
|
||||||
"@opencode-ai/plugin": ["@opencode-ai/plugin@workspace:packages/plugin"],
|
"@opencode-ai/plugin": ["@opencode-ai/plugin@workspace:packages/plugin"],
|
||||||
|
|
||||||
"@opencode-ai/protocol": ["@opencode-ai/protocol@workspace:packages/protocol"],
|
"@opencode-ai/protocol": ["@opencode-ai/protocol@workspace:packages/protocol"],
|
||||||
@@ -2271,27 +2300,27 @@
|
|||||||
|
|
||||||
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="],
|
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="],
|
||||||
|
|
||||||
"@opentui/core": ["@opentui/core@0.4.3", "", { "dependencies": { "bun-ffi-structs": "0.2.4", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.4.3", "@opentui/core-darwin-x64": "0.4.3", "@opentui/core-linux-arm64": "0.4.3", "@opentui/core-linux-arm64-musl": "0.4.3", "@opentui/core-linux-x64": "0.4.3", "@opentui/core-linux-x64-musl": "0.4.3", "@opentui/core-win32-arm64": "0.4.3", "@opentui/core-win32-x64": "0.4.3" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-rrJfAk13tALDqldYjhc78eWQ+aKq1iknJgffIOg3OwyZoqQo+p6gtuqyhmWvXIfQzlNUbpgpCPcxbXlhMnlaHQ=="],
|
"@opentui/core": ["@opentui/core@0.4.5", "", { "dependencies": { "bun-ffi-structs": "0.2.4", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.4.5", "@opentui/core-darwin-x64": "0.4.5", "@opentui/core-linux-arm64": "0.4.5", "@opentui/core-linux-arm64-musl": "0.4.5", "@opentui/core-linux-x64": "0.4.5", "@opentui/core-linux-x64-musl": "0.4.5", "@opentui/core-win32-arm64": "0.4.5", "@opentui/core-win32-x64": "0.4.5" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-JsgRTPkA6e+Vxmumxai6SElOSlRQkbzNKHlCfemlArRiLhfC1IZ9RXJo2QH4xSu+uBOWAM90uss73/pPlkdEig=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.4.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-p5+7AAxpxGuDGagyQfewKtmTFnN7THvTVY4FyKqUtJomNaHdQXPHztapNNzMx0DGWbwOUbVKzpL+yc3CZY3chQ=="],
|
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.4.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8KUG0oRidnR+oW1RSZJ72/PhZLl+qRRMk5U/mieF4c0SJ5V3tYACpBZAKzQfHNd1f7QzD8FHZct1lPpQgtmkWg=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.4.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-+fh0vEUE0lwVC7RW5ijYLRlTLp5NfvCRj8SzxDVd7IL2j2ssB6YXcfIbXq2EW7UGnrejwPRXf1tgUrIXW9KmOw=="],
|
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.4.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-R2bocsg55gwjOqCp/MWFgFYzRmsduKegB6nzgFAPCvAD/L5Jf30xpWJWFlSg3x8vxe1L9WJ84dfqa4M7mZZ3wA=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.4.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-gl6qA5QJy6u8Cbt7gOtHbhhfMZ4qQDb0kEwFXHcMGmbnKzz4OHoq74D6tNjyvSQB9saoC7C6C0tvn2DcJOuNog=="],
|
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-R4MZ25a4CzOAGVjW9aj1hUfzQGVfCJwrwBDbNs2SXaIvzcZqkxCVtU4FoQ5LsaD0j/BdNQVg2CIfFkFsm1fDuQ=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.4.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-8p8g8/AEq/xFGpQ7XcIFKcAqjc0QwsZcv+Ll9RbCDpUA56FGH6jfLDir0KYTNTgYXJTIrBIENI9K46VuxMUMQA=="],
|
"@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ieqdyKI6EIYPalYAETB2wsdP83hr5Ifi+dFnBFUmdEEFHsoKwBmn2S7bsTOYlX7Bg03F4/YPIg+IvRpeC+cUJw=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.4.3", "", { "os": "linux", "cpu": "x64" }, "sha512-dXpJitiZdYE3hq2Pvx6e9I0uPQSOcnaLLp1pDgWAHv+3kvKSHEX//9Yr/pV/Ua6qqT7p+2D/K4vXNap/NKVo2w=="],
|
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-SNyuQoxMKI1vuJhgxSSW96adWM6LqFl2SoS3GM4tGeneGOanVVG2Y06PvlytXvF4cKik97t0rqkVMRetmOs93w=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.4.3", "", { "os": "linux", "cpu": "x64" }, "sha512-/QiFpCrpU2O7vy8QYmLIQYbvAtKDgmqcVjR7dGtqSzkiQk3ktNJoo5RozG7ueXnjung1Wp0nKldKxo2Csg/OrA=="],
|
"@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-mKVKcIcPiSVVZZsdPSBoWwoa2/TCeQAaMDeHF7PFw2kt5bTXZPP7xxWfRQLCNIcA1eaGl59UuwUWHDR2Ve548Q=="],
|
||||||
|
|
||||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.4.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-Mx2zuOjrhm/z2SDS6RExIyjP/SnN/8QhhagxURUw0jQi/NssGSeAllu1cBAFFnhobJL5QLTE4FU4CRhUK9svgg=="],
|
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.4.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-GHTTsqeR45q2Iek9Rb7ty+x/hAKn2jZ1ujlCgPR8LBKyF7h0E1dNFryoZ7ehMc3kJndP1sKn836IemKFqxuDdQ=="],
|
||||||
|
|
||||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.4.3", "", { "os": "win32", "cpu": "x64" }, "sha512-NuoqvWKGXaYnmlqvu7Gg2lLI6yVMnS9OfWBvxp+7Q+McSgHFSTQmYBXaPpvQ8HikpQXE1nCeMPtuSG4PdZHe2w=="],
|
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.4.5", "", { "os": "win32", "cpu": "x64" }, "sha512-Y8T/yXCDGagRGiQrtmuB6AhRcPucKFs/Dre3v8kJwNYqDccI4FzUPKclZ7djfmRZNjl7JUqPhZZP/PwDpQocMg=="],
|
||||||
|
|
||||||
"@opentui/keymap": ["@opentui/keymap@0.4.3", "", { "dependencies": { "@opentui/core": "0.4.3" }, "peerDependencies": { "@opentui/react": "0.4.3", "@opentui/solid": "0.4.3", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-sinX0pyQBRrEvo89PSSUbSUDIYpL3xWo81VEfec58VFoVRB5FG48/deAtvRTQfJ8w1kgbzN8hzdOXdSm61zBmw=="],
|
"@opentui/keymap": ["@opentui/keymap@0.4.5", "", { "dependencies": { "@opentui/core": "0.4.5" }, "peerDependencies": { "@opentui/react": "0.4.5", "@opentui/solid": "0.4.5", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-S1wzKHhF70zT6bH+VBFY+lSeTImLcIFW28JNQiME8MoPcy6KGPs7rKFSHrb/U7P8rsTJeRfW5A4d1Cy6PKodDg=="],
|
||||||
|
|
||||||
"@opentui/solid": ["@opentui/solid@0.4.3", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.4.3", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-RcV0+S8HMdXOASyr7HmJUBuTUIaFPzAxMDa44VftS5C2JUgrmAuWo0Njv1q3TWRB1owjHnyKhEfWGKq7A82wxw=="],
|
"@opentui/solid": ["@opentui/solid@0.4.5", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.4.5", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-B0RSkXnrtPVfEJOX+Hj+axjLJ3lzbG1BZw5I7Pvb9OPp48Vzg2cW2a3cSa86/q48ndLt647i/XwFPIw/jqnI5g=="],
|
||||||
|
|
||||||
"@orama/orama": ["@orama/orama@3.1.18", "", {}, "sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA=="],
|
"@orama/orama": ["@orama/orama@3.1.18", "", {}, "sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA=="],
|
||||||
|
|
||||||
@@ -2871,7 +2900,7 @@
|
|||||||
|
|
||||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.6", "", { "dependencies": { "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-tHhdiWZfG1ZIh2YcRfPJmY2gHcBmqbAzqm3ER4TIDFYsSEqTD5tICT7cgQ/kI8LRakxp12myOYyK68XPn7MnHw=="],
|
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.6", "", { "dependencies": { "@smithy/core": "^3.24.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-tHhdiWZfG1ZIh2YcRfPJmY2gHcBmqbAzqm3ER4TIDFYsSEqTD5tICT7cgQ/kI8LRakxp12myOYyK68XPn7MnHw=="],
|
||||||
|
|
||||||
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.7", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ=="],
|
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="],
|
||||||
|
|
||||||
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.3.5", "", { "dependencies": { "@smithy/core": "^3.24.5", "tslib": "^2.6.2" } }, "sha512-M9rMkTar7JcRrvUHsK1271AuWDmrISIPQpQ4TSHmYZ4KMisGnMH0gfjCWnBwdndR7skvvp/UheHhZGvO3Cr8/g=="],
|
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.3.5", "", { "dependencies": { "@smithy/core": "^3.24.5", "tslib": "^2.6.2" } }, "sha512-M9rMkTar7JcRrvUHsK1271AuWDmrISIPQpQ4TSHmYZ4KMisGnMH0gfjCWnBwdndR7skvvp/UheHhZGvO3Cr8/g=="],
|
||||||
|
|
||||||
@@ -2945,7 +2974,7 @@
|
|||||||
|
|
||||||
"@smithy/util-stream": ["@smithy/util-stream@4.6.5", "", { "dependencies": { "@smithy/core": "^3.24.5", "tslib": "^2.6.2" } }, "sha512-PFzBVEBP5k8R+mK/c+VAKmtpUTL+KzBIXWJ6oM0GWOb31K+QgymXV9IW03XLPM1wtkC7oAb9ZBN2aswSSVbNFg=="],
|
"@smithy/util-stream": ["@smithy/util-stream@4.6.5", "", { "dependencies": { "@smithy/core": "^3.24.5", "tslib": "^2.6.2" } }, "sha512-PFzBVEBP5k8R+mK/c+VAKmtpUTL+KzBIXWJ6oM0GWOb31K+QgymXV9IW03XLPM1wtkC7oAb9ZBN2aswSSVbNFg=="],
|
||||||
|
|
||||||
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="],
|
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
||||||
|
|
||||||
"@smithy/util-waiter": ["@smithy/util-waiter@4.4.5", "", { "dependencies": { "@smithy/core": "^3.24.5", "tslib": "^2.6.2" } }, "sha512-EYviebytZE6vplW0AGwZ2Rc3sNuVR83lfUCNZu11VchUiKhMwJqrRWy7iVDTNEwG/vEwItno591Iad6/prj6Bw=="],
|
"@smithy/util-waiter": ["@smithy/util-waiter@4.4.5", "", { "dependencies": { "@smithy/core": "^3.24.5", "tslib": "^2.6.2" } }, "sha512-EYviebytZE6vplW0AGwZ2Rc3sNuVR83lfUCNZu11VchUiKhMwJqrRWy7iVDTNEwG/vEwItno591Iad6/prj6Bw=="],
|
||||||
|
|
||||||
@@ -3007,7 +3036,7 @@
|
|||||||
|
|
||||||
"@standard-community/standard-openapi": ["@standard-community/standard-openapi@0.2.9", "", { "peerDependencies": { "@standard-community/standard-json": "^0.3.5", "@standard-schema/spec": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.17.14", "openapi-types": "^12.1.3", "sury": "^10.0.0", "typebox": "^1.0.0", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-openapi": "^4" }, "optionalPeers": ["arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-openapi"] }, "sha512-htj+yldvN1XncyZi4rehbf9kLbu8os2Ke/rfqoZHCMHuw34kiF3LP/yQPdA0tQ940y8nDq3Iou8R3wG+AGGyvg=="],
|
"@standard-community/standard-openapi": ["@standard-community/standard-openapi@0.2.9", "", { "peerDependencies": { "@standard-community/standard-json": "^0.3.5", "@standard-schema/spec": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.17.14", "openapi-types": "^12.1.3", "sury": "^10.0.0", "typebox": "^1.0.0", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-openapi": "^4" }, "optionalPeers": ["arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-openapi"] }, "sha512-htj+yldvN1XncyZi4rehbf9kLbu8os2Ke/rfqoZHCMHuw34kiF3LP/yQPdA0tQ940y8nDq3Iou8R3wG+AGGyvg=="],
|
||||||
|
|
||||||
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"@stoplight/better-ajv-errors": ["@stoplight/better-ajv-errors@1.0.3", "", { "dependencies": { "jsonpointer": "^5.0.0", "leven": "^3.1.0" }, "peerDependencies": { "ajv": ">=8" } }, "sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA=="],
|
"@stoplight/better-ajv-errors": ["@stoplight/better-ajv-errors@1.0.3", "", { "dependencies": { "jsonpointer": "^5.0.0", "leven": "^3.1.0" }, "peerDependencies": { "ajv": ">=8" } }, "sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA=="],
|
||||||
|
|
||||||
@@ -4015,7 +4044,7 @@
|
|||||||
|
|
||||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||||
|
|
||||||
"effect": ["effect@4.0.0-beta.83", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-0wsak8RtgGAr9UWSbVDgJHZcUqMSvicHcvaZv1MbMM7MCGgW4Rn/137J1MHQbwYPcwYGxT/IqehFd+UbYuj78w=="],
|
"effect": ["effect@4.0.0-beta.98", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.9.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.4", "multipasta": "^0.2.8", "toml": "^4.1.2", "uuid": "^14.0.1", "yaml": "^2.9.0" } }, "sha512-oz+bsG5h+6RNrw4t5GMfQrk/xBS8ROoqkYsuvRhBr5O7mCOrpvH/hbw+QrDzvKIpX4HJClwm86F94c87W0sJxg=="],
|
||||||
|
|
||||||
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
|
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
|
||||||
|
|
||||||
@@ -4191,7 +4220,7 @@
|
|||||||
|
|
||||||
"extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="],
|
"extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="],
|
||||||
|
|
||||||
"fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="],
|
"fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="],
|
||||||
|
|
||||||
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
|
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
|
||||||
|
|
||||||
@@ -5055,7 +5084,7 @@
|
|||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
"msgpackr": ["msgpackr@2.0.2", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-c5hYOXFbP79Slh6Dzd2wzk+jnV7mX1UxfMYtilnY1NmalXPqG8DGb5cYCMBrW4AsH3zekBBZd4QrKz9NhtvYLQ=="],
|
"msgpackr": ["msgpackr@2.0.4", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-o1C5KRmuRt+apqMr1HuGSqWStZoRBUpEsCsl15uM9VdAF1qHLtvMOU2En747EnTyEl6c4pzPewRMFF31s1CNbA=="],
|
||||||
|
|
||||||
"msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="],
|
"msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="],
|
||||||
|
|
||||||
@@ -5063,7 +5092,7 @@
|
|||||||
|
|
||||||
"multicast-dns": ["multicast-dns@7.2.5", "", { "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" }, "bin": { "multicast-dns": "cli.js" } }, "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg=="],
|
"multicast-dns": ["multicast-dns@7.2.5", "", { "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" }, "bin": { "multicast-dns": "cli.js" } }, "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg=="],
|
||||||
|
|
||||||
"multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="],
|
"multipasta": ["multipasta@0.2.8", "", {}, "sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q=="],
|
||||||
|
|
||||||
"mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="],
|
"mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="],
|
||||||
|
|
||||||
@@ -5593,6 +5622,8 @@
|
|||||||
|
|
||||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||||
|
|
||||||
|
"resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="],
|
||||||
|
|
||||||
"responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="],
|
"responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="],
|
||||||
|
|
||||||
"restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
|
"restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
|
||||||
@@ -5961,7 +5992,7 @@
|
|||||||
|
|
||||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||||
|
|
||||||
"toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="],
|
"toml": ["toml@4.3.0", "", {}, "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A=="],
|
||||||
|
|
||||||
"toolbeam-docs-theme": ["toolbeam-docs-theme@0.4.8", "", { "peerDependencies": { "@astrojs/starlight": "^0.34.3", "astro": "^5.7.13" } }, "sha512-b+5ynEFp4Woe5a22hzNQm42lD23t13ZMihVxHbzjA50zdcM9aOSJTIjdJ0PDSd4/50HbBXcpHiQsz6rM4N88ww=="],
|
"toolbeam-docs-theme": ["toolbeam-docs-theme@0.4.8", "", { "peerDependencies": { "@astrojs/starlight": "^0.34.3", "astro": "^5.7.13" } }, "sha512-b+5ynEFp4Woe5a22hzNQm42lD23t13ZMihVxHbzjA50zdcM9aOSJTIjdJ0PDSd4/50HbBXcpHiQsz6rM4N88ww=="],
|
||||||
|
|
||||||
@@ -6047,7 +6078,7 @@
|
|||||||
|
|
||||||
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
|
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
|
||||||
|
|
||||||
"undici": ["undici@8.3.0", "", {}, "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q=="],
|
"undici": ["undici@8.7.0", "", {}, "sha512-N7iQtfyLhIMOFgQubvmLV26svHpO0bqKnAiWotTQCVKCmWrcGbBotPuW1x+xwYZ2VHdSTVUfPQQnlEt1/LouTQ=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
|
||||||
@@ -6129,7 +6160,7 @@
|
|||||||
|
|
||||||
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
|
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
|
||||||
|
|
||||||
"uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="],
|
"uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="],
|
||||||
|
|
||||||
"valibot": ["valibot@1.4.1", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-klCmFTz2jeDluy9RwX+F884TCiogtdBJ/YaxSx1EOBYXa3NXNWj8kR1jjN8rzluwojJVWWaHJ4r1U5LfICnM3g=="],
|
"valibot": ["valibot@1.4.1", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-klCmFTz2jeDluy9RwX+F884TCiogtdBJ/YaxSx1EOBYXa3NXNWj8kR1jjN8rzluwojJVWWaHJ4r1U5LfICnM3g=="],
|
||||||
|
|
||||||
@@ -6335,10 +6366,6 @@
|
|||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="],
|
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
"@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||||
@@ -6395,8 +6422,6 @@
|
|||||||
|
|
||||||
"@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
"@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||||
|
|
||||||
"@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/togetherai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
"@ai-sdk/togetherai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||||
|
|
||||||
"@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
"@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||||
@@ -6457,26 +6482,18 @@
|
|||||||
|
|
||||||
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||||
|
|
||||||
"@aws-sdk/client-athena/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"@aws-sdk/client-cognito-identity/@aws-sdk/core": ["@aws-sdk/core@3.974.15", "", { "dependencies": { "@aws-sdk/types": "^3.973.9", "@aws-sdk/xml-builder": "^3.972.26", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.5", "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-UpA0rTGW/tHGITcCqHisbuuEPraYg9GG+mWmXjY5+RxZBMLGe6aL9oe0ix50LztwAcPIkGZLH0yWdMIkCM10hw=="],
|
"@aws-sdk/client-cognito-identity/@aws-sdk/core": ["@aws-sdk/core@3.974.15", "", { "dependencies": { "@aws-sdk/types": "^3.973.9", "@aws-sdk/xml-builder": "^3.972.26", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.5", "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-UpA0rTGW/tHGITcCqHisbuuEPraYg9GG+mWmXjY5+RxZBMLGe6aL9oe0ix50LztwAcPIkGZLH0yWdMIkCM10hw=="],
|
||||||
|
|
||||||
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.47", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.41", "@aws-sdk/credential-provider-http": "^3.972.43", "@aws-sdk/credential-provider-ini": "^3.972.46", "@aws-sdk/credential-provider-process": "^3.972.41", "@aws-sdk/credential-provider-sso": "^3.972.45", "@aws-sdk/credential-provider-web-identity": "^3.972.45", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/credential-provider-imds": "^4.3.6", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-HrId+C0DWA5qDIyLG64/kjUB2RNtPypxmABnIctK+TA1P1kHlOYoE/Wf5T5tKOMKgb08P7k/zNyhvfJ3lh5Oag=="],
|
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.47", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.41", "@aws-sdk/credential-provider-http": "^3.972.43", "@aws-sdk/credential-provider-ini": "^3.972.46", "@aws-sdk/credential-provider-process": "^3.972.41", "@aws-sdk/credential-provider-sso": "^3.972.45", "@aws-sdk/credential-provider-web-identity": "^3.972.45", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/credential-provider-imds": "^4.3.6", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-HrId+C0DWA5qDIyLG64/kjUB2RNtPypxmABnIctK+TA1P1kHlOYoE/Wf5T5tKOMKgb08P7k/zNyhvfJ3lh5Oag=="],
|
||||||
|
|
||||||
"@aws-sdk/client-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.973.9", "", { "dependencies": { "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg=="],
|
"@aws-sdk/client-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.973.9", "", { "dependencies": { "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg=="],
|
||||||
|
|
||||||
"@aws-sdk/client-firehose/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"@aws-sdk/client-lambda/@aws-sdk/core": ["@aws-sdk/core@3.974.15", "", { "dependencies": { "@aws-sdk/types": "^3.973.9", "@aws-sdk/xml-builder": "^3.972.26", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.5", "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-UpA0rTGW/tHGITcCqHisbuuEPraYg9GG+mWmXjY5+RxZBMLGe6aL9oe0ix50LztwAcPIkGZLH0yWdMIkCM10hw=="],
|
"@aws-sdk/client-lambda/@aws-sdk/core": ["@aws-sdk/core@3.974.15", "", { "dependencies": { "@aws-sdk/types": "^3.973.9", "@aws-sdk/xml-builder": "^3.972.26", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.5", "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-UpA0rTGW/tHGITcCqHisbuuEPraYg9GG+mWmXjY5+RxZBMLGe6aL9oe0ix50LztwAcPIkGZLH0yWdMIkCM10hw=="],
|
||||||
|
|
||||||
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.47", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.41", "@aws-sdk/credential-provider-http": "^3.972.43", "@aws-sdk/credential-provider-ini": "^3.972.46", "@aws-sdk/credential-provider-process": "^3.972.41", "@aws-sdk/credential-provider-sso": "^3.972.45", "@aws-sdk/credential-provider-web-identity": "^3.972.45", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/credential-provider-imds": "^4.3.6", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-HrId+C0DWA5qDIyLG64/kjUB2RNtPypxmABnIctK+TA1P1kHlOYoE/Wf5T5tKOMKgb08P7k/zNyhvfJ3lh5Oag=="],
|
"@aws-sdk/client-lambda/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.47", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.41", "@aws-sdk/credential-provider-http": "^3.972.43", "@aws-sdk/credential-provider-ini": "^3.972.46", "@aws-sdk/credential-provider-process": "^3.972.41", "@aws-sdk/credential-provider-sso": "^3.972.45", "@aws-sdk/credential-provider-web-identity": "^3.972.45", "@aws-sdk/types": "^3.973.9", "@smithy/core": "^3.24.5", "@smithy/credential-provider-imds": "^4.3.6", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-HrId+C0DWA5qDIyLG64/kjUB2RNtPypxmABnIctK+TA1P1kHlOYoE/Wf5T5tKOMKgb08P7k/zNyhvfJ3lh5Oag=="],
|
||||||
|
|
||||||
"@aws-sdk/client-lambda/@aws-sdk/types": ["@aws-sdk/types@3.973.9", "", { "dependencies": { "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg=="],
|
"@aws-sdk/client-lambda/@aws-sdk/types": ["@aws-sdk/types@3.973.9", "", { "dependencies": { "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg=="],
|
||||||
|
|
||||||
"@aws-sdk/client-s3/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"@aws-sdk/client-sso/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"@aws-sdk/client-sts/@aws-sdk/core": ["@aws-sdk/core@3.775.0", "", { "dependencies": { "@aws-sdk/types": "3.775.0", "@smithy/core": "^3.2.0", "@smithy/node-config-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.0.2", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA=="],
|
"@aws-sdk/client-sts/@aws-sdk/core": ["@aws-sdk/core@3.775.0", "", { "dependencies": { "@aws-sdk/types": "3.775.0", "@smithy/core": "^3.2.0", "@smithy/node-config-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.0.2", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA=="],
|
||||||
|
|
||||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.782.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.775.0", "@aws-sdk/credential-provider-http": "3.775.0", "@aws-sdk/credential-provider-ini": "3.782.0", "@aws-sdk/credential-provider-process": "3.775.0", "@aws-sdk/credential-provider-sso": "3.782.0", "@aws-sdk/credential-provider-web-identity": "3.782.0", "@aws-sdk/types": "3.775.0", "@smithy/credential-provider-imds": "^4.0.2", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-HZiAF+TCEyKjju9dgysjiPIWgt/+VerGaeEp18mvKLNfgKz1d+/82A2USEpNKTze7v3cMFASx3CvL8yYyF7mJw=="],
|
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.782.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.775.0", "@aws-sdk/credential-provider-http": "3.775.0", "@aws-sdk/credential-provider-ini": "3.782.0", "@aws-sdk/credential-provider-process": "3.775.0", "@aws-sdk/credential-provider-sso": "3.782.0", "@aws-sdk/credential-provider-web-identity": "3.782.0", "@aws-sdk/types": "3.775.0", "@smithy/credential-provider-imds": "^4.0.2", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-HZiAF+TCEyKjju9dgysjiPIWgt/+VerGaeEp18mvKLNfgKz1d+/82A2USEpNKTze7v3cMFASx3CvL8yYyF7mJw=="],
|
||||||
@@ -6499,10 +6516,6 @@
|
|||||||
|
|
||||||
"@aws-sdk/client-sts/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.782.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/types": "3.775.0", "@smithy/node-config-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-dMFkUBgh2Bxuw8fYZQoH/u3H4afQ12VSkzEi//qFiDTwbKYq+u+RYjc8GLDM6JSK1BShMu5AVR7HD4ap1TYUnA=="],
|
"@aws-sdk/client-sts/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.782.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/types": "3.775.0", "@smithy/node-config-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-dMFkUBgh2Bxuw8fYZQoH/u3H4afQ12VSkzEi//qFiDTwbKYq+u+RYjc8GLDM6JSK1BShMu5AVR7HD4ap1TYUnA=="],
|
||||||
|
|
||||||
"@aws-sdk/client-sts/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.973.9", "", { "dependencies": { "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg=="],
|
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.973.9", "", { "dependencies": { "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-env/@aws-sdk/core": ["@aws-sdk/core@3.974.15", "", { "dependencies": { "@aws-sdk/types": "^3.973.9", "@aws-sdk/xml-builder": "^3.972.26", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.5", "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-UpA0rTGW/tHGITcCqHisbuuEPraYg9GG+mWmXjY5+RxZBMLGe6aL9oe0ix50LztwAcPIkGZLH0yWdMIkCM10hw=="],
|
"@aws-sdk/credential-provider-env/@aws-sdk/core": ["@aws-sdk/core@3.974.15", "", { "dependencies": { "@aws-sdk/types": "^3.973.9", "@aws-sdk/xml-builder": "^3.972.26", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.5", "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-UpA0rTGW/tHGITcCqHisbuuEPraYg9GG+mWmXjY5+RxZBMLGe6aL9oe0ix50LztwAcPIkGZLH0yWdMIkCM10hw=="],
|
||||||
@@ -6551,10 +6564,6 @@
|
|||||||
|
|
||||||
"@aws-sdk/credential-providers/@aws-sdk/types": ["@aws-sdk/types@3.973.9", "", { "dependencies": { "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg=="],
|
"@aws-sdk/credential-providers/@aws-sdk/types": ["@aws-sdk/types@3.973.9", "", { "dependencies": { "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-kuBfgQVdcz5Bmapc4A13YbpVw/pXkesfhetcFYwbntqas8sF41OHyd4o28+/TG2ZQdHBsv90Lsu5y6oitvYCdg=="],
|
||||||
|
|
||||||
"@aws-sdk/middleware-flexible-checksums/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"@aws-sdk/middleware-sdk-s3/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"@aws-sdk/nested-clients/@aws-sdk/core": ["@aws-sdk/core@3.974.15", "", { "dependencies": { "@aws-sdk/types": "^3.973.9", "@aws-sdk/xml-builder": "^3.972.26", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.5", "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-UpA0rTGW/tHGITcCqHisbuuEPraYg9GG+mWmXjY5+RxZBMLGe6aL9oe0ix50LztwAcPIkGZLH0yWdMIkCM10hw=="],
|
"@aws-sdk/nested-clients/@aws-sdk/core": ["@aws-sdk/core@3.974.15", "", { "dependencies": { "@aws-sdk/types": "^3.973.9", "@aws-sdk/xml-builder": "^3.972.26", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.5", "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-UpA0rTGW/tHGITcCqHisbuuEPraYg9GG+mWmXjY5+RxZBMLGe6aL9oe0ix50LztwAcPIkGZLH0yWdMIkCM10hw=="],
|
||||||
|
|
||||||
"@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.30", "", { "dependencies": { "@aws-sdk/types": "^3.973.9", "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-HULDLMVzkmTSEv6//7kx2kRevp/VYUpm8hJNNFbmhxDn0fUiGTxVcM9yg31TukvTq8nyOBDUN2gH0o5IRbKjdw=="],
|
"@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.30", "", { "dependencies": { "@aws-sdk/types": "^3.973.9", "@smithy/signature-v4": "^5.4.5", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-HULDLMVzkmTSEv6//7kx2kRevp/VYUpm8hJNNFbmhxDn0fUiGTxVcM9yg31TukvTq8nyOBDUN2gH0o5IRbKjdw=="],
|
||||||
@@ -6629,6 +6638,8 @@
|
|||||||
|
|
||||||
"@hey-api/openapi-ts/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
"@hey-api/openapi-ts/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||||
|
|
||||||
|
"@hono/standard-validator/@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
||||||
|
|
||||||
"@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
"@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||||
|
|
||||||
"@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
|
"@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
|
||||||
@@ -6849,6 +6860,10 @@
|
|||||||
|
|
||||||
"@opencode-ai/cli/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
"@opencode-ai/cli/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||||
|
|
||||||
|
"@opencode-ai/console-app/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.7", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ=="],
|
||||||
|
|
||||||
|
"@opencode-ai/console-app/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="],
|
||||||
|
|
||||||
"@opencode-ai/console-mail/@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="],
|
"@opencode-ai/console-mail/@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="],
|
||||||
|
|
||||||
"@opencode-ai/core/@ai-sdk/openai": ["@ai-sdk/openai@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cmgbeJL0bbY0yTJH4/AdmP5E7MjWRL9G8UdhIi0JlV/So03o82ORJofW8OzwCZPTORVQblFbpZXYGDcUd9NdUQ=="],
|
"@opencode-ai/core/@ai-sdk/openai": ["@ai-sdk/openai@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cmgbeJL0bbY0yTJH4/AdmP5E7MjWRL9G8UdhIi0JlV/So03o82ORJofW8OzwCZPTORVQblFbpZXYGDcUd9NdUQ=="],
|
||||||
@@ -6863,10 +6878,6 @@
|
|||||||
|
|
||||||
"@opencode-ai/desktop/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="],
|
"@opencode-ai/desktop/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="],
|
||||||
|
|
||||||
"@opencode-ai/ai/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="],
|
|
||||||
|
|
||||||
"@opencode-ai/ai/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"@opencode-ai/script/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
"@opencode-ai/script/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@opencode-ai/session-ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="],
|
"@opencode-ai/session-ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="],
|
||||||
@@ -7351,8 +7362,6 @@
|
|||||||
|
|
||||||
"editorconfig/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
|
"editorconfig/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
|
||||||
|
|
||||||
"effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"electron-builder/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
"electron-builder/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
|
|
||||||
"electron-builder/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
"electron-builder/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||||
@@ -7413,8 +7422,6 @@
|
|||||||
|
|
||||||
"fumadocs-core/shiki": ["shiki@4.3.1", "", { "dependencies": { "@shikijs/core": "4.3.1", "@shikijs/engine-javascript": "4.3.1", "@shikijs/engine-oniguruma": "4.3.1", "@shikijs/langs": "4.3.1", "@shikijs/themes": "4.3.1", "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw=="],
|
"fumadocs-core/shiki": ["shiki@4.3.1", "", { "dependencies": { "@shikijs/core": "4.3.1", "@shikijs/engine-javascript": "4.3.1", "@shikijs/engine-oniguruma": "4.3.1", "@shikijs/langs": "4.3.1", "@shikijs/themes": "4.3.1", "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw=="],
|
||||||
|
|
||||||
"fumadocs-mdx/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"fumadocs-mdx/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
"fumadocs-mdx/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||||
|
|
||||||
"fumadocs-mdx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
"fumadocs-mdx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
||||||
@@ -7553,6 +7560,8 @@
|
|||||||
|
|
||||||
"opencode/@solid-primitives/scheduled": ["@solid-primitives/scheduled@1.5.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA=="],
|
"opencode/@solid-primitives/scheduled": ["@solid-primitives/scheduled@1.5.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA=="],
|
||||||
|
|
||||||
|
"opencode/@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
||||||
|
|
||||||
"opencode/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
|
"opencode/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
|
||||||
|
|
||||||
"opencode/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
"opencode/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
@@ -7759,46 +7768,6 @@
|
|||||||
|
|
||||||
"@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
|
"@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/azure/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/cerebras/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/cohere/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/deepgram/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/deepinfra/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/deepseek/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/elevenlabs/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/fireworks/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/google-vertex/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/google/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/groq/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/mistral/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/openai-compatible/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/perplexity/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/togetherai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@astrojs/check/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
"@astrojs/check/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||||
|
|
||||||
"@astrojs/check/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
"@astrojs/check/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
@@ -8401,9 +8370,25 @@
|
|||||||
|
|
||||||
"@solidjs/start/shiki/@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
|
"@solidjs/start/shiki/@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
|
||||||
|
|
||||||
"@standard-community/standard-json/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@standard-community/standard-json/effect/fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="],
|
||||||
|
|
||||||
"@standard-community/standard-openapi/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@standard-community/standard-json/effect/msgpackr": ["msgpackr@2.0.2", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-c5hYOXFbP79Slh6Dzd2wzk+jnV7mX1UxfMYtilnY1NmalXPqG8DGb5cYCMBrW4AsH3zekBBZd4QrKz9NhtvYLQ=="],
|
||||||
|
|
||||||
|
"@standard-community/standard-json/effect/multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="],
|
||||||
|
|
||||||
|
"@standard-community/standard-json/effect/toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="],
|
||||||
|
|
||||||
|
"@standard-community/standard-json/effect/uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="],
|
||||||
|
|
||||||
|
"@standard-community/standard-openapi/effect/fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="],
|
||||||
|
|
||||||
|
"@standard-community/standard-openapi/effect/msgpackr": ["msgpackr@2.0.2", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-c5hYOXFbP79Slh6Dzd2wzk+jnV7mX1UxfMYtilnY1NmalXPqG8DGb5cYCMBrW4AsH3zekBBZd4QrKz9NhtvYLQ=="],
|
||||||
|
|
||||||
|
"@standard-community/standard-openapi/effect/multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="],
|
||||||
|
|
||||||
|
"@standard-community/standard-openapi/effect/toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="],
|
||||||
|
|
||||||
|
"@standard-community/standard-openapi/effect/uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="],
|
||||||
|
|
||||||
"@stoplight/spectral-core/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="],
|
"@stoplight/spectral-core/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="],
|
||||||
|
|
||||||
@@ -8479,10 +8464,6 @@
|
|||||||
|
|
||||||
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||||
|
|
||||||
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="],
|
|
||||||
|
|
||||||
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||||
|
|
||||||
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||||
@@ -8873,10 +8854,6 @@
|
|||||||
|
|
||||||
"venice-ai-sdk-provider/@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="],
|
"venice-ai-sdk-provider/@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="],
|
||||||
|
|
||||||
"venice-ai-sdk-provider/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"vitest/@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
|
"vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
|
||||||
|
|
||||||
"wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
|
"wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
|
||||||
@@ -8989,12 +8966,8 @@
|
|||||||
|
|
||||||
"@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="],
|
"@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="],
|
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="],
|
"@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="],
|
"@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="],
|
||||||
@@ -9145,8 +9118,6 @@
|
|||||||
|
|
||||||
"@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
"@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||||
|
|
||||||
"@opencode-ai/core/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
|
"@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
|
||||||
|
|
||||||
"@opencode-ai/www/@cloudflare/vite-plugin/@cloudflare/unenv-preset/workerd": ["workerd@1.20260708.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260708.1", "@cloudflare/workerd-darwin-arm64": "1.20260708.1", "@cloudflare/workerd-linux-64": "1.20260708.1", "@cloudflare/workerd-linux-arm64": "1.20260708.1", "@cloudflare/workerd-windows-64": "1.20260708.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-WAK+Kt/VVCSldH2qSr8lx46XCJ4Q+bdlHNaFqUtOHthBEIB8C1N8HVW+VOLrxDoTCk0NGNv0zajnBeQK4JOB9w=="],
|
"@opencode-ai/www/@cloudflare/vite-plugin/@cloudflare/unenv-preset/workerd": ["workerd@1.20260708.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260708.1", "@cloudflare/workerd-darwin-arm64": "1.20260708.1", "@cloudflare/workerd-linux-64": "1.20260708.1", "@cloudflare/workerd-linux-arm64": "1.20260708.1", "@cloudflare/workerd-windows-64": "1.20260708.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-WAK+Kt/VVCSldH2qSr8lx46XCJ4Q+bdlHNaFqUtOHthBEIB8C1N8HVW+VOLrxDoTCk0NGNv0zajnBeQK4JOB9w=="],
|
||||||
@@ -9329,14 +9300,6 @@
|
|||||||
|
|
||||||
"@vitejs/plugin-react/vite/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
"@vitejs/plugin-react/vite/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||||
|
|
||||||
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"ai-gateway-provider/@ai-sdk/azure/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"ai-gateway-provider/@ai-sdk/deepseek/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
|
||||||
|
|
||||||
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
"app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
|
"app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
|
||||||
@@ -9479,8 +9442,6 @@
|
|||||||
|
|
||||||
"@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="],
|
"@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="],
|
"@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="],
|
||||||
|
|
||||||
"@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="],
|
"@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="],
|
||||||
|
|||||||
+11
-8
@@ -37,18 +37,18 @@
|
|||||||
"packages/slack"
|
"packages/slack"
|
||||||
],
|
],
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"@effect/opentelemetry": "4.0.0-beta.83",
|
"@effect/opentelemetry": "4.0.0-beta.98",
|
||||||
"@effect/platform-node": "4.0.0-beta.83",
|
"@effect/platform-node": "4.0.0-beta.98",
|
||||||
"@effect/sql-sqlite-bun": "4.0.0-beta.83",
|
"@effect/sql-sqlite-bun": "4.0.0-beta.98",
|
||||||
"@npmcli/arborist": "9.4.0",
|
"@npmcli/arborist": "9.4.0",
|
||||||
"@types/bun": "1.3.13",
|
"@types/bun": "1.3.13",
|
||||||
"@types/cross-spawn": "6.0.6",
|
"@types/cross-spawn": "6.0.6",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@hono/standard-validator": "0.2.0",
|
"@hono/standard-validator": "0.2.0",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"@opentui/core": "0.4.3",
|
"@opentui/core": "0.4.5",
|
||||||
"@opentui/keymap": "0.4.3",
|
"@opentui/keymap": "0.4.5",
|
||||||
"@opentui/solid": "0.4.3",
|
"@opentui/solid": "0.4.5",
|
||||||
"@tanstack/solid-virtual": "3.13.32",
|
"@tanstack/solid-virtual": "3.13.32",
|
||||||
"@shikijs/stream": "4.2.0",
|
"@shikijs/stream": "4.2.0",
|
||||||
"ulid": "3.0.1",
|
"ulid": "3.0.1",
|
||||||
@@ -69,12 +69,13 @@
|
|||||||
"dompurify": "3.3.1",
|
"dompurify": "3.3.1",
|
||||||
"drizzle-kit": "1.0.0-rc.2",
|
"drizzle-kit": "1.0.0-rc.2",
|
||||||
"drizzle-orm": "1.0.0-rc.2",
|
"drizzle-orm": "1.0.0-rc.2",
|
||||||
"effect": "4.0.0-beta.83",
|
"effect": "4.0.0-beta.98",
|
||||||
"ai": "6.0.168",
|
"ai": "6.0.168",
|
||||||
"cross-spawn": "7.0.6",
|
"cross-spawn": "7.0.6",
|
||||||
"hono": "4.10.7",
|
"hono": "4.10.7",
|
||||||
"hono-openapi": "1.1.2",
|
"hono-openapi": "1.1.2",
|
||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
|
"get-east-asian-width": "1.6.0",
|
||||||
"luxon": "3.6.1",
|
"luxon": "3.6.1",
|
||||||
"marked": "17.0.6",
|
"marked": "17.0.6",
|
||||||
"marked-shiki": "1.2.1",
|
"marked-shiki": "1.2.1",
|
||||||
@@ -85,9 +86,11 @@
|
|||||||
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
||||||
"zod": "4.1.8",
|
"zod": "4.1.8",
|
||||||
"remeda": "2.26.0",
|
"remeda": "2.26.0",
|
||||||
|
"resolve.exports": "2.0.3",
|
||||||
"sst": "4.13.1",
|
"sst": "4.13.1",
|
||||||
"shiki": "4.2.0",
|
"shiki": "4.2.0",
|
||||||
"solid-list": "0.3.0",
|
"solid-list": "0.3.0",
|
||||||
|
"string-width": "7.2.0",
|
||||||
"tailwindcss": "4.1.11",
|
"tailwindcss": "4.1.11",
|
||||||
"vite": "7.1.4",
|
"vite": "7.1.4",
|
||||||
"@solidjs/meta": "0.29.4",
|
"@solidjs/meta": "0.29.4",
|
||||||
@@ -163,7 +166,7 @@
|
|||||||
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
||||||
"@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch",
|
"@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch",
|
||||||
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
|
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
|
||||||
"effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch",
|
"effect@4.0.0-beta.98": "patches/effect@4.0.0-beta.98.patch",
|
||||||
"@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch"
|
"@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+62
-7
@@ -1,6 +1,6 @@
|
|||||||
# @opencode-ai/ai
|
# @opencode-ai/ai
|
||||||
|
|
||||||
Schema-first LLM core for opencode. One typed request, response, event, and tool language; provider quirks live in adapters, not in calling code.
|
Schema-first AI primitives for opencode. Provider quirks live in adapters, not in calling code.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
@@ -24,6 +24,45 @@ const program = Effect.gen(function* () {
|
|||||||
|
|
||||||
Run `LLMClient.stream(request)` instead of `generate` when you want incremental `LLMEvent`s. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses, Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment.
|
Run `LLMClient.stream(request)` instead of `generate` when you want incremental `LLMEvent`s. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses, Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment.
|
||||||
|
|
||||||
|
## Image generation
|
||||||
|
|
||||||
|
Use `Image.generate` with an image model for direct asset generation:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Image } from "@opencode-ai/ai"
|
||||||
|
import { OpenAI } from "@opencode-ai/ai/providers"
|
||||||
|
|
||||||
|
const program = Effect.gen(function* () {
|
||||||
|
const response = yield* Image.generate({
|
||||||
|
model: OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY }).image("gpt-image-2"),
|
||||||
|
prompt: "A robot tending a rooftop garden",
|
||||||
|
count: 2,
|
||||||
|
size: { width: 1024, height: 1024 },
|
||||||
|
providerOptions: { openai: { quality: "high", outputFormat: "webp" } },
|
||||||
|
})
|
||||||
|
|
||||||
|
return response.images // GeneratedImage[] with owned bytes or a provider URL
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Conversational image generation remains part of the LLM interaction. OpenAI Responses exposes it through its hosted image tool:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const program = Effect.gen(function* () {
|
||||||
|
const response = yield* LLM.generate(
|
||||||
|
LLM.request({
|
||||||
|
model: OpenAI.configure({ apiKey }).responses("gpt-5"),
|
||||||
|
prompt: "Design a solarpunk rooftop garden, then show me.",
|
||||||
|
tools: [OpenAI.imageGeneration({ quality: "high" })],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
return response.message
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
The hosted result is represented as a provider-executed tool call and tool result. Its image is a `file` content item with a data URI, so retaining `response.message` preserves the generated image for continuation.
|
||||||
|
|
||||||
## Public API
|
## Public API
|
||||||
|
|
||||||
- **`LLM.request({...})`** — build a provider-neutral `LLMRequest`. Accepts ergonomic inputs (`system: string`, `prompt: string`) that normalize into the canonical Schema classes.
|
- **`LLM.request({...})`** — build a provider-neutral `LLMRequest`. Accepts ergonomic inputs (`system: string`, `prompt: string`) that normalize into the canonical Schema classes.
|
||||||
@@ -32,6 +71,8 @@ Run `LLMClient.stream(request)` instead of `generate` when you want incremental
|
|||||||
- **`Model.make(...)` / `ToolCallPart.make(...)` / `ToolResultPart.make(...)` / `ToolDefinition.make(...)`** — model and tool-related constructors from the canonical schema model.
|
- **`Model.make(...)` / `ToolCallPart.make(...)` / `ToolResultPart.make(...)` / `ToolDefinition.make(...)`** — model and tool-related constructors from the canonical schema model.
|
||||||
- **`LLMClient.prepare(request)`** — compile a request through protocol body construction, validation, and HTTP preparation without sending. Useful for inspection and testing.
|
- **`LLMClient.prepare(request)`** — compile a request through protocol body construction, validation, and HTTP preparation without sending. Useful for inspection and testing.
|
||||||
- **`LLMEvent.is.*`** — typed guards (`is.textDelta`, `is.toolCall`, `is.finish`, …) for filtering streams.
|
- **`LLMEvent.is.*`** — typed guards (`is.textDelta`, `is.toolCall`, `is.finish`, …) for filtering streams.
|
||||||
|
- **`Image.generate({...})`** — generate images through a provider-neutral image request and response model.
|
||||||
|
- **`ImageClient`** — Effect service and layer for image execution, parallel to `LLMClient`.
|
||||||
|
|
||||||
## Caching
|
## Caching
|
||||||
|
|
||||||
@@ -127,23 +168,37 @@ OpenAI Chat and OpenAI Responses are separate semantic entrypoints:
|
|||||||
- `@opencode-ai/ai/providers/openai/responses`
|
- `@opencode-ai/ai/providers/openai/responses`
|
||||||
- `@opencode-ai/ai/providers/openai-compatible/responses`
|
- `@opencode-ai/ai/providers/openai-compatible/responses`
|
||||||
- `@opencode-ai/ai/providers/anthropic-compatible`
|
- `@opencode-ai/ai/providers/anthropic-compatible`
|
||||||
- `@opencode-ai/ai/providers/google-vertex`
|
- `@opencode-ai/ai/providers/google-vertex/gemini`
|
||||||
- `@opencode-ai/ai/providers/google-vertex/anthropic`
|
- `@opencode-ai/ai/providers/google-vertex/chat`
|
||||||
|
- `@opencode-ai/ai/providers/google-vertex/responses`
|
||||||
|
- `@opencode-ai/ai/providers/google-vertex/messages`
|
||||||
|
|
||||||
Responses HTTP versus WebSocket is a scoped `transport` setting on the OpenAI Responses entrypoint, not another entrypoint. Azure follows the same Chat/Responses split at `providers/azure/chat` and `providers/azure/responses`. Generic OpenAI-compatible Chat remains at `providers/openai-compatible`; compatible Responses is separate at `providers/openai-compatible/responses`. Generic Anthropic Messages-compatible providers use `providers/anthropic-compatible`, which the named Anthropic provider composes. Google Gemini and Amazon Bedrock expose their single native API through their existing provider paths.
|
Responses HTTP versus WebSocket is a scoped `transport` setting on the OpenAI Responses entrypoint, not another entrypoint. Azure follows the same Chat/Responses split at `providers/azure/chat` and `providers/azure/responses`. Generic OpenAI-compatible Chat remains at `providers/openai-compatible`; compatible Responses is separate at `providers/openai-compatible/responses`. Generic Anthropic Messages-compatible providers use `providers/anthropic-compatible`, which the named Anthropic provider composes. Google Gemini and Amazon Bedrock expose their single native API through their existing provider paths.
|
||||||
|
|
||||||
Vertex Gemini and Vertex Anthropic are separate products with separate entrypoints. Both accept `project`, `location`, and an optional `accessToken`; when no explicit token or auth override is supplied they lazily use Google Application Default Credentials. Vertex Gemini instead selects express mode when `apiKey` or `GOOGLE_VERTEX_API_KEY` is present.
|
Vertex Gemini, Vertex Chat, Vertex Responses, and Vertex Messages are separate API entrypoints. All accept `project`, `location`, and an optional `accessToken`; when no explicit token or auth override is supplied they lazily use Google Application Default Credentials. Vertex Gemini instead selects express mode when `apiKey` or `GOOGLE_VERTEX_API_KEY` is present. Vertex Chat targets MaaS models through the OpenAI-compatible Chat Completions endpoint, while Vertex Responses targets Grok models and defaults `store` to `false` as required by Vertex. `providers/google-vertex` remains the default alias for `providers/google-vertex/gemini`.
|
||||||
|
|
||||||
Tuned Vertex Gemini deployments use model ids shaped like `endpoints/1234567890` and require OAuth or ADC; Vertex express-mode API keys support publisher models only.
|
Tuned Vertex Gemini deployments use model ids shaped like `endpoints/1234567890` and require OAuth or ADC; Vertex express-mode API keys support publisher models only.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { model } from "@opencode-ai/ai/providers/google-vertex"
|
import { model } from "@opencode-ai/ai/providers/google-vertex/gemini"
|
||||||
|
|
||||||
model("gemini-3.5-flash", { project: "my-project", location: "global" })
|
model("gemini-3.5-flash", { project: "my-project", location: "global" })
|
||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { model } from "@opencode-ai/ai/providers/google-vertex/anthropic"
|
import { model } from "@opencode-ai/ai/providers/google-vertex/chat"
|
||||||
|
|
||||||
|
model("deepseek-ai/deepseek-v3.2-maas", { project: "my-project", location: "global" })
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { model } from "@opencode-ai/ai/providers/google-vertex/responses"
|
||||||
|
|
||||||
|
model("xai/grok-4.20-reasoning", { project: "my-project", location: "global" })
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { model } from "@opencode-ai/ai/providers/google-vertex/messages"
|
||||||
|
|
||||||
model("claude-sonnet-4-6", { project: "my-project", location: "global" })
|
model("claude-sonnet-4-6", { project: "my-project", location: "global" })
|
||||||
```
|
```
|
||||||
@@ -168,7 +223,7 @@ Adding a new model or deployment is usually 5-15 lines using `Route.make({ proto
|
|||||||
|
|
||||||
## Effect
|
## Effect
|
||||||
|
|
||||||
This package is built on Effect. Public methods return `Effect` or `Stream`; provide `LLMClient.layer` for runtime dispatch and import the provider/protocol modules for the routes you use. The example at `example/tutorial.ts` is a runnable walkthrough.
|
This package is built on Effect. Public methods return `Effect` or `Stream`; provide `LLMClient.layer` for LLM dispatch and `ImageClient.layer` for image dispatch, then import the provider/protocol modules for the routes you use. The example at `example/tutorial.ts` is a runnable walkthrough.
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
|
|||||||
+24
-22
@@ -1,15 +1,15 @@
|
|||||||
# LLM Provider Parity Status
|
# LLM Provider Parity Status
|
||||||
|
|
||||||
Last reviewed: 2026-07-15
|
Last reviewed: 2026-07-17
|
||||||
|
|
||||||
This file tracks the gap between the native `@opencode-ai/ai` package and the AI SDK provider packages that opencode still depends on for many catalog/runtime paths.
|
This file tracks the gap between the native `@opencode-ai/ai` package and the AI SDK provider packages that opencode still depends on for many catalog/runtime paths.
|
||||||
|
|
||||||
## Existing Status Sources
|
## Existing Status Sources
|
||||||
|
|
||||||
| File | What it tracks | Limitation |
|
| File | What it tracks | Limitation |
|
||||||
| ------------------------------------ | -------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
| ----------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||||
| `packages/ai/DESIGN.md` | Future clean-break API proposal for `@opencode-ai/ai`. | Not a provider parity tracker. |
|
| `packages/ai/DESIGN.md` | Future clean-break API proposal for `@opencode-ai/ai`. | Not a provider parity tracker. |
|
||||||
| `packages/ai/example/call-sites.md` | Route/value/provider-facade migration checklist and call-site sketches. | Architecture migration only; not AI SDK package parity. |
|
| `packages/ai/example/call-sites.md` | Route/value/provider-facade migration checklist and call-site sketches. | Architecture migration only; not AI SDK package parity. |
|
||||||
|
|
||||||
## Current Implementation Snapshot
|
## Current Implementation Snapshot
|
||||||
|
|
||||||
@@ -20,11 +20,13 @@ This file tracks the gap between the native `@opencode-ai/ai` package and the AI
|
|||||||
| OpenAI Responses WebSocket | `src/protocols/openai-responses.ts`, `src/route/transport/websocket.ts` | Present as `OpenAI.responsesWebSocket(...)`. | Runner/catalog support explicitly must not downgrade WebSocket routes; broader runtime selection is not complete. |
|
| OpenAI Responses WebSocket | `src/protocols/openai-responses.ts`, `src/route/transport/websocket.ts` | Present as `OpenAI.responsesWebSocket(...)`. | Runner/catalog support explicitly must not downgrade WebSocket routes; broader runtime selection is not complete. |
|
||||||
| OpenAI-compatible Chat | `src/protocols/openai-compatible-chat.ts`, `src/providers/openai-compatible.ts` | Usable for generic Chat and several profiles: Baseten, Cerebras, DeepInfra, DeepSeek, Fireworks, Groq, TogetherAI. | Family quirks are mostly endpoint defaults, not full typed behavior. |
|
| OpenAI-compatible Chat | `src/protocols/openai-compatible-chat.ts`, `src/providers/openai-compatible.ts` | Usable for generic Chat and several profiles: Baseten, Cerebras, DeepInfra, DeepSeek, Fireworks, Groq, TogetherAI. | Family quirks are mostly endpoint defaults, not full typed behavior. |
|
||||||
| OpenAI-compatible Responses | `src/protocols/openai-compatible-responses.ts`, `src/providers/openai-compatible-responses.ts` | Usable for deployments that implement the OpenAI Responses wire protocol. | No named family profiles or recorded deployment coverage yet. |
|
| OpenAI-compatible Responses | `src/protocols/openai-compatible-responses.ts`, `src/providers/openai-compatible-responses.ts` | Usable for deployments that implement the OpenAI Responses wire protocol. | No named family profiles or recorded deployment coverage yet. |
|
||||||
| Anthropic-compatible Messages | `src/protocols/anthropic-messages.ts`, `src/providers/anthropic-compatible.ts` | Usable for deployments that implement the Anthropic Messages wire protocol. Named Anthropic composes this base. | No named compatible family profiles or recorded deployment coverage yet. |
|
| Anthropic-compatible Messages | `src/protocols/anthropic-messages.ts`, `src/providers/anthropic-compatible.ts` | Usable for deployments that implement the Anthropic Messages wire protocol. Named Anthropic composes this base; MiniMax M3 has recorded text and tool-loop coverage. | No named compatible family profiles yet. |
|
||||||
| Anthropic Messages | `src/protocols/anthropic-messages.ts`, `src/providers/anthropic.ts` | Usable. Supports tools, thinking, cache control, images, server-hosted tool events, and usage. | Provider option surface is small. Beta/header handling, metadata, and newer Messages fields need a typed parity pass. |
|
| Anthropic Messages | `src/protocols/anthropic-messages.ts`, `src/providers/anthropic.ts` | Usable. Supports tools, thinking, cache control, images, server-hosted tool events, and usage. | Provider option surface is small. Beta/header handling, metadata, and newer Messages fields need a typed parity pass. |
|
||||||
| Gemini Developer API | `src/protocols/gemini.ts`, `src/providers/google.ts` | Usable for Google API key flow. Supports text, images, tools, thinking signatures, and cache usage. | This is not Vertex. Typed provider options are narrow; many Gemini request fields currently require raw `http.body` overlays. |
|
| Gemini Developer API | `src/protocols/gemini.ts`, `src/providers/google.ts` | Usable for Google API key flow. Supports text, images, tools, thinking signatures, and cache usage. | This is not Vertex. Typed provider options are narrow; many Gemini request fields currently require raw `http.body` overlays. |
|
||||||
| Vertex Gemini | `src/protocols/google-vertex-gemini.ts`, `src/providers/google-vertex.ts` | Usable through API-key express mode, explicit OAuth tokens, or ADC with project/location endpoint derivation, including tuned `endpoints/...` deployments. | Core runner/catalog mapping and recorded provider coverage are missing. |
|
| Vertex Gemini | `src/protocols/gemini.ts`, `src/providers/google-vertex.ts` | Usable through API-key express mode, explicit OAuth tokens, or ADC with project/location endpoint derivation, including tuned `endpoints/...` deployments. | Core runner/catalog mapping and recorded provider coverage are missing. |
|
||||||
| Vertex Anthropic Messages | `src/protocols/google-vertex-anthropic.ts`, `src/providers/google-vertex-anthropic.ts` | Usable through explicit OAuth tokens or ADC, including global, regional, and `eu`/`us` multi-region endpoints. | Core runner/catalog mapping and recorded provider coverage are missing; Vertex-specific hosted-tool parity needs review. |
|
| Vertex Chat | `src/protocols/openai-chat.ts`, `src/providers/google-vertex-chat.ts` | Usable for MaaS models through OpenAI-compatible Chat Completions with explicit OAuth tokens or ADC and project/location endpoint derivation. | Core runner/catalog mapping and recorded provider coverage are missing; MaaS family-specific request parity needs review. |
|
||||||
|
| Vertex Responses | `src/protocols/openai-responses.ts`, `src/providers/google-vertex-responses.ts` | Usable for Grok models through OpenAI-compatible Responses with explicit OAuth tokens or ADC, project/location endpoint derivation, and storage disabled by default. | Core runner/catalog mapping and recorded provider coverage are missing; stateful continuation is not supported by Vertex. |
|
||||||
|
| Vertex Messages | `src/protocols/anthropic-messages.ts`, `src/providers/google-vertex-messages.ts` | Usable through explicit OAuth tokens or ADC, including global, regional, and `eu`/`us` multi-region endpoints. | Core runner/catalog mapping and recorded provider coverage are missing; Vertex-specific hosted-tool parity needs review. |
|
||||||
| Bedrock Converse | `src/protocols/bedrock-converse.ts`, `src/providers/amazon-bedrock.ts` | Partial but real. Supports AWS event-stream framing, SigV4 with supplied credentials, bearer auth, tools, reasoning signatures, media, cache points, and recorded tests. | Native facade does not mirror the AI SDK plugin's default AWS credential chain/profile behavior. Runner/catalog mapping is missing. Guardrails, inference profiles, region-specific model ID fixes, and model-specific request fields need a parity pass. |
|
| Bedrock Converse | `src/protocols/bedrock-converse.ts`, `src/providers/amazon-bedrock.ts` | Partial but real. Supports AWS event-stream framing, SigV4 with supplied credentials, bearer auth, tools, reasoning signatures, media, cache points, and recorded tests. | Native facade does not mirror the AI SDK plugin's default AWS credential chain/profile behavior. Runner/catalog mapping is missing. Guardrails, inference profiles, region-specific model ID fixes, and model-specific request fields need a parity pass. |
|
||||||
| Azure OpenAI | `src/providers/azure.ts` using OpenAI Chat/Responses protocols | Partial. Supports resource/base URL setup, API key auth, API version query, Chat, and Responses selectors. | Core runner does not map `@ai-sdk/azure` to this native facade. AAD/token auth and Azure-specific endpoint variants need review. |
|
| Azure OpenAI | `src/providers/azure.ts` using OpenAI Chat/Responses protocols | Partial. Supports resource/base URL setup, API key auth, API version query, Chat, and Responses selectors. | Core runner does not map `@ai-sdk/azure` to this native facade. AAD/token auth and Azure-specific endpoint variants need review. |
|
||||||
| Cloudflare AI Gateway / Workers AI | `src/providers/cloudflare.ts` | Present via OpenAI-compatible Chat routes. | Useful but not part of the critical AI SDK replacement set yet. Needs per-product recorded coverage before relying on it broadly. |
|
| Cloudflare AI Gateway / Workers AI | `src/providers/cloudflare.ts` | Present via OpenAI-compatible Chat routes. | Useful but not part of the critical AI SDK replacement set yet. Needs per-product recorded coverage before relying on it broadly. |
|
||||||
@@ -54,8 +56,8 @@ Other `aisdk:` packages, including Google Vertex, Azure, and Bedrock, currently
|
|||||||
| `@ai-sdk/google` | Gemini Developer API | Partial / usable | Add typed options for safety, response schema/modalities, cached content, grounding/search/code execution, and non-text output modes where supported. |
|
| `@ai-sdk/google` | Gemini Developer API | Partial / usable | Add typed options for safety, response schema/modalities, cached content, grounding/search/code execution, and non-text output modes where supported. |
|
||||||
| `@ai-sdk/google-vertex` | Vertex Gemini namespace/facade | Partial / usable | Add runner/catalog mapping, recorded coverage, and broader provider-option parity. |
|
| `@ai-sdk/google-vertex` | Vertex Gemini namespace/facade | Partial / usable | Add runner/catalog mapping, recorded coverage, and broader provider-option parity. |
|
||||||
| `@ai-sdk/google-vertex/anthropic` | Anthropic Messages over Vertex namespace/facade | Partial / usable | Add runner/catalog mapping, recorded coverage, and Vertex-specific hosted-tool parity. |
|
| `@ai-sdk/google-vertex/anthropic` | Anthropic Messages over Vertex namespace/facade | Partial / usable | Add runner/catalog mapping, recorded coverage, and Vertex-specific hosted-tool parity. |
|
||||||
| `@ai-sdk/google-vertex/maas` | Vertex MaaS OpenAI-compatible namespace/facade | Missing | Decide native Chat/Responses selection, endpoint derivation, auth, and catalog mapping. |
|
| `@ai-sdk/google-vertex/maas` | Vertex Chat | Partial / usable | Add runner/catalog mapping, recorded coverage, and MaaS family-specific request parity. |
|
||||||
| `@ai-sdk/google-vertex/xai` | Vertex xAI OpenAI-compatible namespace/facade | Missing | Decide whether this composes the generic compatible bases or the xAI facade, then add endpoint/auth mapping and tests. |
|
| `@ai-sdk/google-vertex/xai` | Vertex Chat / Responses | Partial / usable | Decide Chat/Responses selection for catalog models, add runner mapping and recorded coverage, and review xAI-specific request options. |
|
||||||
| `@ai-sdk/azure` | Azure OpenAI Chat/Responses facade | Partial | Map runner/catalog metadata to native Azure, handle resourceName/baseURL/apiVersion variants, add AAD/token auth story, and verify Chat vs Responses deployment selection. |
|
| `@ai-sdk/azure` | Azure OpenAI Chat/Responses facade | Partial | Map runner/catalog metadata to native Azure, handle resourceName/baseURL/apiVersion variants, add AAD/token auth story, and verify Chat vs Responses deployment selection. |
|
||||||
| `@ai-sdk/amazon-bedrock` | Bedrock Converse | Partial | Add default AWS credential chain/profile support, region/inference-profile model ID handling, provider option parity via `additionalModelRequestFields`, guardrails/performance config, and runner/catalog mapping. |
|
| `@ai-sdk/amazon-bedrock` | Bedrock Converse | Partial | Add default AWS credential chain/profile support, region/inference-profile model ID handling, provider option parity via `additionalModelRequestFields`, guardrails/performance config, and runner/catalog mapping. |
|
||||||
| `@ai-sdk/amazon-bedrock/mantle` | Bedrock Mantle OpenAI-compatible Chat/Responses namespace | Missing | Decide native Mantle shape, likely separate from Converse because it uses OpenAI-compatible Chat/Responses semantics over Bedrock. Add package mapping and tests. |
|
| `@ai-sdk/amazon-bedrock/mantle` | Bedrock Mantle OpenAI-compatible Chat/Responses namespace | Missing | Decide native Mantle shape, likely separate from Converse because it uses OpenAI-compatible Chat/Responses semantics over Bedrock. Add package mapping and tests. |
|
||||||
@@ -65,19 +67,19 @@ Other `aisdk:` packages, including Google Vertex, Azure, and Bedrock, currently
|
|||||||
1. Runner support is narrower than the LLM package. The package has native provider facades for Google, Azure, and Bedrock, but the V2 Session runner only maps OpenAI, Anthropic, and explicit OpenAI-compatible Chat from `aisdk` catalog metadata.
|
1. Runner support is narrower than the LLM package. The package has native provider facades for Google, Azure, and Bedrock, but the V2 Session runner only maps OpenAI, Anthropic, and explicit OpenAI-compatible Chat from `aisdk` catalog metadata.
|
||||||
2. OpenAI-compatible Responses is available as a separate package entrypoint, but the V2 runner still maps `@ai-sdk/openai-compatible` to Chat only. Catalog selection must become API-aware before Responses deployments can use it.
|
2. OpenAI-compatible Responses is available as a separate package entrypoint, but the V2 runner still maps `@ai-sdk/openai-compatible` to Chat only. Catalog selection must become API-aware before Responses deployments can use it.
|
||||||
3. Bedrock native auth is not AI SDK parity. The AI SDK plugin uses the default AWS provider chain, profile, container credentials, and Bedrock bearer token env behavior. Native Bedrock currently expects explicit credentials or bearer auth on the facade.
|
3. Bedrock native auth is not AI SDK parity. The AI SDK plugin uses the default AWS provider chain, profile, container credentials, and Bedrock bearer token env behavior. Native Bedrock currently expects explicit credentials or bearer auth on the facade.
|
||||||
4. Vertex Gemini and Vertex Anthropic now have native package entrypoints, but the core runner does not map catalog metadata to them yet and recorded provider coverage is still missing.
|
4. Vertex Gemini, Vertex Chat, Vertex Responses, and Vertex Messages now have native package entrypoints, but the core runner does not map catalog metadata to them yet and recorded provider coverage is still missing.
|
||||||
5. Azure is only a provider facade, not a full runtime replacement. Native Azure exists, but the catalog runner does not select it, and token auth/resource variants need review.
|
5. Azure is only a provider facade, not a full runtime replacement. Native Azure exists, but the catalog runner does not select it, and token auth/resource variants need review.
|
||||||
6. Provider option typing is uneven. OpenAI, Anthropic, Gemini, Bedrock, and OpenRouter each expose a small typed subset plus raw HTTP overlays; this is useful but not equivalent to AI SDK provider option coverage.
|
6. Provider option typing is uneven. OpenAI, Anthropic, Gemini, Bedrock, and OpenRouter each expose a small typed subset plus raw HTTP overlays; this is useful but not equivalent to AI SDK provider option coverage.
|
||||||
7. Structured output is not provider-native yet. `LLM.generateObject` still uses a synthetic tool strategy, while the future design expects native structured output where reliable and tool fallback where needed.
|
7. Structured output is not provider-native yet. `LLM.generateObject` still uses a synthetic tool strategy, while the future design expects native structured output where reliable and tool fallback where needed.
|
||||||
8. Package/namespace boundaries for the current native loading set are explicit in docs and exports. Other exported provider facades are not catalog package entrypoints until they implement the contract. Missing native boundaries remain for Vertex MaaS, Vertex xAI, and Bedrock Mantle.
|
8. Package/namespace boundaries for the current native loading set are explicit in docs and exports. Other exported provider facades are not catalog package entrypoints until they implement the contract. Vertex xAI still needs catalog API selection; the missing native boundary is Bedrock Mantle.
|
||||||
9. Recorded coverage is uneven. OpenAI, Anthropic, Gemini, Bedrock Converse, Cloudflare, OpenRouter, and several OpenAI-compatible Chat providers have cassettes. Azure, Vertex, and Mantle need first-class recorded scenarios before switching defaults.
|
9. Recorded coverage is uneven. OpenAI, Anthropic, Gemini, Bedrock Converse, Cloudflare, OpenRouter, and several OpenAI-compatible Chat providers have cassettes. Azure, Vertex, and Mantle need first-class recorded scenarios before switching defaults.
|
||||||
|
|
||||||
## Native Namespace Shape
|
## Native Namespace Shape
|
||||||
|
|
||||||
These are implementation/API slices, not separate npm packages.
|
These are implementation/API slices, not separate npm packages.
|
||||||
|
|
||||||
| API slice | Package-like entrypoint | Purpose |
|
| API slice | Package-like entrypoint | Purpose |
|
||||||
| ----------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
| ----------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||||
| OpenAI Chat | `@opencode-ai/ai/providers/openai/chat` | OpenAI `/chat/completions` semantics. |
|
| OpenAI Chat | `@opencode-ai/ai/providers/openai/chat` | OpenAI `/chat/completions` semantics. |
|
||||||
| OpenAI Responses | `@opencode-ai/ai/providers/openai/responses` | OpenAI `/responses` semantics with HTTP/WebSocket selected through settings. |
|
| OpenAI Responses | `@opencode-ai/ai/providers/openai/responses` | OpenAI `/responses` semantics with HTTP/WebSocket selected through settings. |
|
||||||
| OpenAI-compatible Chat | `@opencode-ai/ai/providers/openai-compatible` | Generic OpenAI-compatible `/chat/completions`. |
|
| OpenAI-compatible Chat | `@opencode-ai/ai/providers/openai-compatible` | Generic OpenAI-compatible `/chat/completions`. |
|
||||||
@@ -85,12 +87,12 @@ These are implementation/API slices, not separate npm packages.
|
|||||||
| Anthropic-compatible Messages | `@opencode-ai/ai/providers/anthropic-compatible` | Generic Anthropic-compatible `/messages`. |
|
| Anthropic-compatible Messages | `@opencode-ai/ai/providers/anthropic-compatible` | Generic Anthropic-compatible `/messages`. |
|
||||||
| Anthropic Messages | `@opencode-ai/ai/providers/anthropic` | Anthropic Messages API. |
|
| Anthropic Messages | `@opencode-ai/ai/providers/anthropic` | Anthropic Messages API. |
|
||||||
| Gemini Developer API | `@opencode-ai/ai/providers/google` | Google AI Studio Gemini API. |
|
| Gemini Developer API | `@opencode-ai/ai/providers/google` | Google AI Studio Gemini API. |
|
||||||
| Vertex Gemini | `@opencode-ai/ai/providers/google-vertex` | Vertex Gemini API. |
|
| Vertex Gemini | `@opencode-ai/ai/providers/google-vertex/gemini` | Vertex Gemini API; `providers/google-vertex` is the default alias. |
|
||||||
| Vertex Anthropic Messages | `@opencode-ai/ai/providers/google-vertex/anthropic` | Vertex-hosted Anthropic Messages API. |
|
| Vertex Chat | `@opencode-ai/ai/providers/google-vertex/chat` | Vertex OpenAI-compatible Chat Completions for MaaS models. |
|
||||||
| Vertex MaaS | Missing | Vertex OpenAI-compatible MaaS APIs. |
|
| Vertex Responses | `@opencode-ai/ai/providers/google-vertex/responses` | Vertex OpenAI-compatible Responses for Grok models. |
|
||||||
| Vertex xAI | Missing | Vertex-hosted xAI APIs. |
|
| Vertex Messages | `@opencode-ai/ai/providers/google-vertex/messages` | Vertex-hosted Anthropic Messages API. |
|
||||||
| Bedrock Converse | `@opencode-ai/ai/providers/amazon-bedrock` | AWS Bedrock Converse API. |
|
| Bedrock Converse | `@opencode-ai/ai/providers/amazon-bedrock` | AWS Bedrock Converse API. |
|
||||||
| Bedrock Mantle | Missing | AWS Bedrock Mantle OpenAI-compatible APIs. |
|
| Bedrock Mantle | Missing | AWS Bedrock Mantle OpenAI-compatible APIs. |
|
||||||
| Azure OpenAI Chat | `@opencode-ai/ai/providers/azure/chat` | Azure specialization of OpenAI Chat. |
|
| Azure OpenAI Chat | `@opencode-ai/ai/providers/azure/chat` | Azure specialization of OpenAI Chat. |
|
||||||
| Azure OpenAI Responses | `@opencode-ai/ai/providers/azure/responses` | Azure specialization of OpenAI Responses. |
|
| Azure OpenAI Responses | `@opencode-ai/ai/providers/azure/responses` | Azure specialization of OpenAI Responses. |
|
||||||
|
|
||||||
@@ -99,8 +101,8 @@ These are implementation/API slices, not separate npm packages.
|
|||||||
1. Add native runner/catalog mappings for `@ai-sdk/azure`, `@ai-sdk/google`, and `@ai-sdk/amazon-bedrock` where the existing native facades are already close.
|
1. Add native runner/catalog mappings for `@ai-sdk/azure`, `@ai-sdk/google`, and `@ai-sdk/amazon-bedrock` where the existing native facades are already close.
|
||||||
2. Add API-aware runner/catalog selection between OpenAI-compatible Chat and Responses.
|
2. Add API-aware runner/catalog selection between OpenAI-compatible Chat and Responses.
|
||||||
3. Bring Bedrock native auth/config to AI SDK parity: region, profile, default AWS credential chain, bearer token env, endpoint override, and cross-region inference profile handling.
|
3. Bring Bedrock native auth/config to AI SDK parity: region, profile, default AWS credential chain, bearer token env, endpoint override, and cross-region inference profile handling.
|
||||||
4. Add runner/catalog mappings and recorded scenarios for the native Vertex Gemini and Vertex Anthropic entrypoints.
|
4. Add runner/catalog mappings and recorded scenarios for the native Vertex Gemini, Chat, Responses, and Messages entrypoints.
|
||||||
5. Add native Vertex MaaS and Vertex xAI entrypoints by composing the compatible bases and shared Vertex auth/endpoint setup.
|
5. Decide Chat/Responses selection for `@ai-sdk/google-vertex/xai` catalog models.
|
||||||
6. Add Bedrock Mantle as a separate OpenAI-compatible Bedrock namespace after deciding whether it uses Chat, Responses, or both by model.
|
6. Add Bedrock Mantle as a separate OpenAI-compatible Bedrock namespace after deciding whether it uses Chat, Responses, or both by model.
|
||||||
7. Expand typed provider options from the existing V1 lowerer knowledge in `packages/core/src/v1/config/provider-options.ts` before adding more raw overlay examples.
|
7. Expand typed provider options from the existing V1 lowerer knowledge in `packages/core/src/v1/config/provider-options.ts` before adding more raw overlay examples.
|
||||||
8. Add recorded provider tests for Azure, Vertex Gemini, Vertex Anthropic, Bedrock credential-chain behavior, and Mantle before making native runtime the default for those packages.
|
8. Add recorded provider tests for Azure, Vertex Gemini, Vertex Chat, Vertex Responses, Vertex Messages, Bedrock credential-chain behavior, and Mantle before making native runtime the default for those packages.
|
||||||
|
|||||||
@@ -360,17 +360,29 @@ import { model } from "@opencode-ai/ai/providers/openai/responses"
|
|||||||
model("gpt-4o", { apiKey, transport: "websocket" })
|
model("gpt-4o", { apiKey, transport: "websocket" })
|
||||||
```
|
```
|
||||||
|
|
||||||
Vertex keeps Gemini and Anthropic Messages as separate package-like entrypoints,
|
Vertex keeps Gemini, Chat, Responses, and Messages as separate package-like entrypoints,
|
||||||
while sharing project/location resolution and ADC authentication internally:
|
while sharing project/location resolution and ADC authentication internally:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { model } from "@opencode-ai/ai/providers/google-vertex"
|
import { model } from "@opencode-ai/ai/providers/google-vertex/gemini"
|
||||||
|
|
||||||
model("gemini-3.5-flash", { project, location: "global" })
|
model("gemini-3.5-flash", { project, location: "global" })
|
||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { model } from "@opencode-ai/ai/providers/google-vertex/anthropic"
|
import { model } from "@opencode-ai/ai/providers/google-vertex/chat"
|
||||||
|
|
||||||
|
model("deepseek-ai/deepseek-v3.2-maas", { project, location: "global" })
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { model } from "@opencode-ai/ai/providers/google-vertex/responses"
|
||||||
|
|
||||||
|
model("xai/grok-4.20-reasoning", { project, location: "global" })
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { model } from "@opencode-ai/ai/providers/google-vertex/messages"
|
||||||
|
|
||||||
model("claude-sonnet-4-6", { project, location: "global" })
|
model("claude-sonnet-4-6", { project, location: "global" })
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -161,6 +161,18 @@ const PROVIDERS: ReadonlyArray<Provider> = [
|
|||||||
vars: [{ name: "TOGETHER_AI_API_KEY" }],
|
vars: [{ name: "TOGETHER_AI_API_KEY" }],
|
||||||
validate: (env) => validateBearer("https://api.together.xyz/v1/models", Redacted.make(env.TOGETHER_AI_API_KEY)),
|
validate: (env) => validateBearer("https://api.together.xyz/v1/models", Redacted.make(env.TOGETHER_AI_API_KEY)),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "minimax",
|
||||||
|
label: "MiniMax",
|
||||||
|
tier: "compatible",
|
||||||
|
note: "Anthropic-compatible Messages text/tool recorded tests",
|
||||||
|
vars: [{ name: "MINIMAX_API_KEY" }],
|
||||||
|
validate: (env) =>
|
||||||
|
HttpClientRequest.get("https://api.minimax.io/anthropic/v1/models").pipe(
|
||||||
|
HttpClientRequest.setHeader("x-api-key", Redacted.value(Redacted.make(env.MINIMAX_API_KEY))),
|
||||||
|
executeRequest,
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "mistral",
|
id: "mistral",
|
||||||
label: "Mistral",
|
label: "Mistral",
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Context, Effect, Layer } from "effect"
|
||||||
|
import { RequestExecutor } from "./route/executor"
|
||||||
|
import type { ImageRequest, ImageResponse } from "./image"
|
||||||
|
import type { LLMError } from "./schema"
|
||||||
|
|
||||||
|
export type Execute = RequestExecutor.Interface["execute"]
|
||||||
|
|
||||||
|
export interface Interface {
|
||||||
|
readonly generate: (request: ImageRequest) => Effect.Effect<ImageResponse, LLMError>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Service extends Context.Service<Service, Interface>()("@opencode/ImageClient") {}
|
||||||
|
|
||||||
|
export const generate = (request: ImageRequest): Effect.Effect<ImageResponse, LLMError> =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const client = yield* Service
|
||||||
|
return yield* client.generate(request)
|
||||||
|
}) as Effect.Effect<ImageResponse, LLMError>
|
||||||
|
|
||||||
|
export const layer: Layer.Layer<Service, never, RequestExecutor.Service> = Layer.effect(
|
||||||
|
Service,
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const executor = yield* RequestExecutor.Service
|
||||||
|
return Service.of({
|
||||||
|
generate: (request) => request.model.route.generate(request, executor.execute),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ImageClient = {
|
||||||
|
Service,
|
||||||
|
layer,
|
||||||
|
generate,
|
||||||
|
} as const
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import { Effect, Schema } from "effect"
|
||||||
|
import { HttpOptions, InvalidRequestReason, LLMError, ModelID, ProviderID, ProviderMetadata, Usage } from "./schema"
|
||||||
|
import { ImageClient, type Execute as ImageExecute } from "./image-client"
|
||||||
|
|
||||||
|
export interface ImageRoute {
|
||||||
|
readonly id: string
|
||||||
|
readonly generate: (request: ImageRequest, execute: ImageExecute) => Effect.Effect<ImageResponse, LLMError>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ImageModel {
|
||||||
|
readonly id: ModelID
|
||||||
|
readonly provider: ProviderID
|
||||||
|
readonly route: ImageRoute
|
||||||
|
readonly defaults?: ImageModelDefaults
|
||||||
|
|
||||||
|
constructor(input: ImageModel.Input) {
|
||||||
|
this.id = input.id
|
||||||
|
this.provider = input.provider
|
||||||
|
this.route = input.route
|
||||||
|
this.defaults = input.defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
static make(input: ImageModel.MakeInput) {
|
||||||
|
return new ImageModel({
|
||||||
|
id: ModelID.make(input.id),
|
||||||
|
provider: ProviderID.make(input.provider),
|
||||||
|
route: input.route,
|
||||||
|
defaults: input.defaults,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ImageModel {
|
||||||
|
export interface Input {
|
||||||
|
readonly id: ModelID
|
||||||
|
readonly provider: ProviderID
|
||||||
|
readonly route: ImageRoute
|
||||||
|
readonly defaults?: ImageModelDefaults
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MakeInput extends Omit<Input, "id" | "provider"> {
|
||||||
|
readonly id: string | ModelID
|
||||||
|
readonly provider: string | ProviderID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImageModelDefaults {
|
||||||
|
readonly providerOptions?: Record<string, Record<string, unknown>>
|
||||||
|
readonly http?: HttpOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ImageModelSchema = Schema.declare((value): value is ImageModel => value instanceof ImageModel, {
|
||||||
|
expected: "Image.Model",
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ImageSize = Schema.Struct({
|
||||||
|
width: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
|
||||||
|
height: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
|
||||||
|
}).annotate({ identifier: "Image.Size" })
|
||||||
|
export type ImageSize = Schema.Schema.Type<typeof ImageSize>
|
||||||
|
|
||||||
|
export class ImageRequest extends Schema.Class<ImageRequest>("Image.Request")({
|
||||||
|
model: ImageModelSchema,
|
||||||
|
prompt: Schema.String,
|
||||||
|
count: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))),
|
||||||
|
size: Schema.optional(ImageSize),
|
||||||
|
aspectRatio: Schema.optional(Schema.String),
|
||||||
|
seed: Schema.optional(Schema.Number),
|
||||||
|
providerOptions: Schema.optional(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Unknown))),
|
||||||
|
http: Schema.optional(HttpOptions),
|
||||||
|
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export type ImageRequestInput = Omit<ConstructorParameters<typeof ImageRequest>[0], "http"> & {
|
||||||
|
readonly http?: HttpOptions.Input
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GeneratedImage extends Schema.Class<GeneratedImage>("Image.Generated")({
|
||||||
|
mediaType: Schema.String,
|
||||||
|
data: Schema.Union([Schema.String, Schema.Uint8Array]),
|
||||||
|
providerMetadata: Schema.optional(ProviderMetadata),
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export class ImageResponse extends Schema.Class<ImageResponse>("Image.Response")({
|
||||||
|
images: Schema.Array(GeneratedImage),
|
||||||
|
usage: Schema.optional(Usage),
|
||||||
|
providerMetadata: Schema.optional(ProviderMetadata),
|
||||||
|
}) {
|
||||||
|
get image() {
|
||||||
|
return this.images[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const request = (input: ImageRequest | ImageRequestInput) => {
|
||||||
|
if (input instanceof ImageRequest) return input
|
||||||
|
return new ImageRequest({
|
||||||
|
...input,
|
||||||
|
http: input.http === undefined ? undefined : HttpOptions.make(input.http),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generate = (input: ImageRequest | ImageRequestInput) =>
|
||||||
|
Effect.try({
|
||||||
|
try: () => request(input),
|
||||||
|
catch: (error) =>
|
||||||
|
new LLMError({
|
||||||
|
module: "Image",
|
||||||
|
method: "generate",
|
||||||
|
reason: new InvalidRequestReason({ message: error instanceof Error ? error.message : String(error) }),
|
||||||
|
}),
|
||||||
|
}).pipe(Effect.flatMap(ImageClient.generate))
|
||||||
|
|
||||||
|
export const Image = {
|
||||||
|
request,
|
||||||
|
generate,
|
||||||
|
} as const
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export { LLMClient } from "./route/client"
|
export { LLMClient } from "./route/client"
|
||||||
|
export { ImageClient } from "./image-client"
|
||||||
export { Auth } from "./route/auth"
|
export { Auth } from "./route/auth"
|
||||||
export { Provider } from "./provider"
|
export { Provider } from "./provider"
|
||||||
export { ProviderPackage } from "./provider-package"
|
export { ProviderPackage } from "./provider-package"
|
||||||
@@ -10,6 +11,9 @@ export type {
|
|||||||
Service as LLMClientService,
|
Service as LLMClientService,
|
||||||
} from "./route/client"
|
} from "./route/client"
|
||||||
export * from "./schema"
|
export * from "./schema"
|
||||||
|
export { GeneratedImage, ImageModel, ImageRequest, ImageResponse, ImageSize } from "./image"
|
||||||
|
export type { ImageModelDefaults, ImageRequestInput, ImageRoute } from "./image"
|
||||||
|
export { Image } from "./image"
|
||||||
export { Tool, ToolFailure, toDefinitions } from "./tool"
|
export { Tool, ToolFailure, toDefinitions } from "./tool"
|
||||||
export { ToolRuntime } from "./tool-runtime"
|
export { ToolRuntime } from "./tool-runtime"
|
||||||
export type { DispatchResult as ToolDispatchResult, ToolSettlement } from "./tool-runtime"
|
export type { DispatchResult as ToolDispatchResult, ToolSettlement } from "./tool-runtime"
|
||||||
|
|||||||
@@ -703,7 +703,14 @@ const onContentBlockStart = (state: ParserState, event: AnthropicEvent): StepRes
|
|||||||
providerExecuted: block.type === "server_tool_use",
|
providerExecuted: block.type === "server_tool_use",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
[...events, LLMEvent.toolInputStart({ id: block.id ?? String(event.index), name: block.name ?? "" })],
|
[
|
||||||
|
...events,
|
||||||
|
LLMEvent.toolInputStart({
|
||||||
|
id: block.id ?? String(event.index),
|
||||||
|
name: block.name ?? "",
|
||||||
|
providerExecuted: block.type === "server_tool_use" ? true : undefined,
|
||||||
|
}),
|
||||||
|
],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -561,7 +561,9 @@ const step = (state: ParserState, event: BedrockEvent) =>
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
...state,
|
...state,
|
||||||
hasToolCalls: resultEvents.some(LLMEvent.is.toolCall) ? true : state.hasToolCalls,
|
hasToolCalls:
|
||||||
|
resultEvents.some((event) => LLMEvent.is.toolCall(event) || LLMEvent.is.toolInputError(event)) ||
|
||||||
|
state.hasToolCalls,
|
||||||
lifecycle,
|
lifecycle,
|
||||||
tools: result.tools,
|
tools: result.tools,
|
||||||
reasoningSignatures: Object.fromEntries(
|
reasoningSignatures: Object.fromEntries(
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { Effect, Schema, Struct } from "effect"
|
|
||||||
import { AnthropicMessages } from "./anthropic-messages"
|
|
||||||
import { Auth } from "../route/auth"
|
|
||||||
import { Route } from "../route/client"
|
|
||||||
import { Endpoint } from "../route/endpoint"
|
|
||||||
import { Framing } from "../route/framing"
|
|
||||||
import { Protocol } from "../route/protocol"
|
|
||||||
|
|
||||||
const VERSION = "vertex-2023-10-16" as const
|
|
||||||
|
|
||||||
export const GoogleVertexAnthropicBody = Schema.Struct({
|
|
||||||
...Struct.omit(AnthropicMessages.AnthropicMessagesBody.fields, ["model"]),
|
|
||||||
anthropic_version: Schema.Literal(VERSION),
|
|
||||||
})
|
|
||||||
export type GoogleVertexAnthropicBody = Schema.Schema.Type<typeof GoogleVertexAnthropicBody>
|
|
||||||
|
|
||||||
export const protocol = Protocol.make({
|
|
||||||
id: "google-vertex-anthropic",
|
|
||||||
body: {
|
|
||||||
schema: GoogleVertexAnthropicBody,
|
|
||||||
from: (request) =>
|
|
||||||
AnthropicMessages.protocol.body.from(request).pipe(
|
|
||||||
Effect.map((body) => ({
|
|
||||||
...Struct.omit(body, ["model"]),
|
|
||||||
anthropic_version: VERSION,
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
stream: AnthropicMessages.protocol.stream,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const route = Route.make({
|
|
||||||
id: "google-vertex-anthropic",
|
|
||||||
provider: "google-vertex-anthropic",
|
|
||||||
providerMetadataKey: "anthropic",
|
|
||||||
protocol,
|
|
||||||
endpoint: Endpoint.path(({ request }) => `/${request.model.id}:streamRawPredict`),
|
|
||||||
auth: Auth.none,
|
|
||||||
framing: Framing.sse,
|
|
||||||
})
|
|
||||||
|
|
||||||
export * as GoogleVertexAnthropic from "./google-vertex-anthropic"
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Gemini } from "./gemini"
|
|
||||||
import { Auth } from "../route/auth"
|
|
||||||
import { Route } from "../route/client"
|
|
||||||
import { Endpoint } from "../route/endpoint"
|
|
||||||
import { Framing } from "../route/framing"
|
|
||||||
|
|
||||||
export const route = Route.make({
|
|
||||||
id: "google-vertex-gemini",
|
|
||||||
provider: "google-vertex",
|
|
||||||
providerMetadataKey: "google",
|
|
||||||
protocol: Gemini.protocol,
|
|
||||||
endpoint: Endpoint.path(({ request }) => {
|
|
||||||
const model = String(request.model.id)
|
|
||||||
return `/${model.startsWith("endpoints/") ? model : `models/${model}`}:streamGenerateContent?alt=sse`
|
|
||||||
}),
|
|
||||||
auth: Auth.none,
|
|
||||||
framing: Framing.sse,
|
|
||||||
})
|
|
||||||
|
|
||||||
export * as GoogleVertexGemini from "./google-vertex-gemini"
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
export * as AnthropicMessages from "./anthropic-messages"
|
export * as AnthropicMessages from "./anthropic-messages"
|
||||||
export * as GoogleVertexAnthropic from "./google-vertex-anthropic"
|
|
||||||
export * as GoogleVertexGemini from "./google-vertex-gemini"
|
|
||||||
export * as BedrockConverse from "./bedrock-converse"
|
export * as BedrockConverse from "./bedrock-converse"
|
||||||
export * as Gemini from "./gemini"
|
export * as Gemini from "./gemini"
|
||||||
export * as OpenAIChat from "./openai-chat"
|
export * as OpenAIChat from "./openai-chat"
|
||||||
|
export * as OpenAIImages from "./openai-images"
|
||||||
export * as OpenAICompatibleChat from "./openai-compatible-chat"
|
export * as OpenAICompatibleChat from "./openai-compatible-chat"
|
||||||
export * as OpenAICompatibleResponses from "./openai-compatible-responses"
|
export * as OpenAICompatibleResponses from "./openai-compatible-responses"
|
||||||
export * as OpenAIResponses from "./openai-responses"
|
export * as OpenAIResponses from "./openai-responses"
|
||||||
|
|||||||
@@ -75,6 +75,9 @@ const OpenAIChatMessage = Schema.Union([
|
|||||||
content: Schema.NullOr(Schema.String),
|
content: Schema.NullOr(Schema.String),
|
||||||
tool_calls: optionalArray(OpenAIChatAssistantToolCall),
|
tool_calls: optionalArray(OpenAIChatAssistantToolCall),
|
||||||
reasoning_content: Schema.optional(Schema.String),
|
reasoning_content: Schema.optional(Schema.String),
|
||||||
|
reasoning: Schema.optional(Schema.String),
|
||||||
|
reasoning_text: Schema.optional(Schema.String),
|
||||||
|
reasoning_details: optionalArray(Schema.Unknown),
|
||||||
}),
|
}),
|
||||||
Schema.Struct({ role: Schema.Literal("tool"), tool_call_id: Schema.String, content: Schema.String }),
|
Schema.Struct({ role: Schema.Literal("tool"), tool_call_id: Schema.String, content: Schema.String }),
|
||||||
]).pipe(Schema.toTaggedUnion("role"))
|
]).pipe(Schema.toTaggedUnion("role"))
|
||||||
@@ -145,6 +148,9 @@ type OpenAIChatToolCallDelta = Schema.Schema.Type<typeof OpenAIChatToolCallDelta
|
|||||||
const OpenAIChatDelta = Schema.Struct({
|
const OpenAIChatDelta = Schema.Struct({
|
||||||
content: optionalNull(Schema.String),
|
content: optionalNull(Schema.String),
|
||||||
reasoning_content: optionalNull(Schema.String),
|
reasoning_content: optionalNull(Schema.String),
|
||||||
|
reasoning: optionalNull(Schema.String),
|
||||||
|
reasoning_text: optionalNull(Schema.String),
|
||||||
|
reasoning_details: optionalNull(Schema.Array(Schema.Unknown)),
|
||||||
tool_calls: optionalNull(Schema.Array(OpenAIChatToolCallDelta)),
|
tool_calls: optionalNull(Schema.Array(OpenAIChatToolCallDelta)),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -160,12 +166,23 @@ export const OpenAIChatEvent = Schema.Struct({
|
|||||||
export type OpenAIChatEvent = Schema.Schema.Type<typeof OpenAIChatEvent>
|
export type OpenAIChatEvent = Schema.Schema.Type<typeof OpenAIChatEvent>
|
||||||
type OpenAIChatRequestMessage = LLMRequest["messages"][number]
|
type OpenAIChatRequestMessage = LLMRequest["messages"][number]
|
||||||
|
|
||||||
|
interface PendingToolDelta {
|
||||||
|
readonly id?: string
|
||||||
|
readonly name?: string
|
||||||
|
readonly input: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface ParserState {
|
export interface ParserState {
|
||||||
readonly tools: ToolStream.State<number>
|
readonly tools: ToolStream.State<number>
|
||||||
|
readonly pendingTools: Partial<Record<number, PendingToolDelta>>
|
||||||
readonly toolCallEvents: ReadonlyArray<LLMEvent>
|
readonly toolCallEvents: ReadonlyArray<LLMEvent>
|
||||||
readonly usage?: Usage
|
readonly usage?: Usage
|
||||||
readonly finishReason?: FinishReason
|
readonly finishReason?: FinishReason
|
||||||
readonly lifecycle: Lifecycle.State
|
readonly lifecycle: Lifecycle.State
|
||||||
|
readonly reasoningField?: "reasoning" | "reasoning_content" | "reasoning_text"
|
||||||
|
readonly reasoningDetails: Array<unknown>
|
||||||
|
readonly reasoningDetailsObserved: boolean
|
||||||
|
readonly reasoningEmitted: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -208,6 +225,20 @@ const lowerMedia = Effect.fn("OpenAIChat.lowerMedia")(function* (part: MediaPart
|
|||||||
const openAICompatibleReasoningContent = (native: unknown) =>
|
const openAICompatibleReasoningContent = (native: unknown) =>
|
||||||
isRecord(native) && typeof native.reasoning_content === "string" ? native.reasoning_content : undefined
|
isRecord(native) && typeof native.reasoning_content === "string" ? native.reasoning_content : undefined
|
||||||
|
|
||||||
|
const reasoningField = (part: ReasoningPart) => {
|
||||||
|
const field = part.providerMetadata?.openai?.reasoningField
|
||||||
|
if (field === "reasoning" || field === "reasoning_content" || field === "reasoning_text") return field
|
||||||
|
}
|
||||||
|
|
||||||
|
const reasoningDetails = (parts: ReadonlyArray<ReasoningPart>, native: unknown) => {
|
||||||
|
const observed = parts.flatMap((part) => {
|
||||||
|
const details = part.providerMetadata?.openai?.reasoningDetails
|
||||||
|
return Array.isArray(details) ? details : []
|
||||||
|
})
|
||||||
|
if (parts.some((part) => Array.isArray(part.providerMetadata?.openai?.reasoningDetails))) return observed
|
||||||
|
if (isRecord(native) && Array.isArray(native.reasoning_details)) return native.reasoning_details
|
||||||
|
}
|
||||||
|
|
||||||
const lowerUserMessage = Effect.fn("OpenAIChat.lowerUserMessage")(function* (message: OpenAIChatRequestMessage) {
|
const lowerUserMessage = Effect.fn("OpenAIChat.lowerUserMessage")(function* (message: OpenAIChatRequestMessage) {
|
||||||
const content: Array<Schema.Schema.Type<typeof OpenAIChatUserContent>> = []
|
const content: Array<Schema.Schema.Type<typeof OpenAIChatUserContent>> = []
|
||||||
for (const part of message.content) {
|
for (const part of message.content) {
|
||||||
@@ -248,14 +279,29 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const text = reasoning.map((part) => part.text).join("")
|
||||||
|
const details = reasoningDetails(reasoning, message.native?.openaiCompatible)
|
||||||
|
const observedField = reasoning.map(reasoningField).find((value) => value !== undefined)
|
||||||
|
const nativeReasoning = openAICompatibleReasoningContent(message.native?.openaiCompatible)
|
||||||
|
const fullyStructured = reasoning.every((part) => Array.isArray(part.providerMetadata?.openai?.reasoningDetails))
|
||||||
|
const field = (() => {
|
||||||
|
if (reasoning.length === 0) return
|
||||||
|
if (observedField !== undefined) return observedField
|
||||||
|
if (nativeReasoning !== undefined) return "reasoning_content"
|
||||||
|
if (!fullyStructured) return "reasoning_content"
|
||||||
|
})()
|
||||||
|
const reasoningContent = (() => {
|
||||||
|
if (reasoning.length === 0) return nativeReasoning
|
||||||
|
if (field === "reasoning_content") return text
|
||||||
|
})()
|
||||||
return {
|
return {
|
||||||
role: "assistant" as const,
|
role: "assistant" as const,
|
||||||
content: content.length === 0 ? null : ProviderShared.joinText(content),
|
content: content.length === 0 ? null : ProviderShared.joinText(content),
|
||||||
tool_calls: toolCalls.length === 0 ? undefined : toolCalls,
|
tool_calls: toolCalls.length === 0 ? undefined : toolCalls,
|
||||||
reasoning_content:
|
reasoning_content: reasoningContent,
|
||||||
reasoning.length > 0
|
reasoning: reasoning.length > 0 && field === "reasoning" ? text : undefined,
|
||||||
? reasoning.map((part) => part.text).join("")
|
reasoning_text: reasoning.length > 0 && field === "reasoning_text" ? text : undefined,
|
||||||
: openAICompatibleReasoningContent(message.native?.openaiCompatible),
|
reasoning_details: details,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -400,6 +446,65 @@ const mapUsage = (usage: OpenAIChatEvent["usage"]): Usage | undefined => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reasoningDelta = (delta: Schema.Schema.Type<typeof OpenAIChatDelta> | null | undefined) => {
|
||||||
|
if (delta?.reasoning_content) return { field: "reasoning_content", text: delta.reasoning_content } as const
|
||||||
|
if (delta?.reasoning) return { field: "reasoning", text: delta.reasoning } as const
|
||||||
|
if (delta?.reasoning_text) return { field: "reasoning_text", text: delta.reasoning_text } as const
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailText = (details: ReadonlyArray<unknown>) => {
|
||||||
|
const text = details.flatMap((detail) => {
|
||||||
|
if (!isRecord(detail)) return []
|
||||||
|
if (detail.type === "reasoning.text" && typeof detail.text === "string" && detail.text) return [detail.text]
|
||||||
|
if (detail.type === "reasoning.summary" && typeof detail.summary === "string" && detail.summary)
|
||||||
|
return [detail.summary]
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
if (text.length > 0) return text.join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
const appendReasoningDetails = (result: Array<unknown>, details: ReadonlyArray<unknown>) => {
|
||||||
|
for (const detail of details) {
|
||||||
|
const previous = result.at(-1)
|
||||||
|
if (
|
||||||
|
!isRecord(previous) ||
|
||||||
|
previous.type !== "reasoning.text" ||
|
||||||
|
!isRecord(detail) ||
|
||||||
|
detail.type !== "reasoning.text" ||
|
||||||
|
conflictingReasoningTextDetails(previous, detail)
|
||||||
|
) {
|
||||||
|
result.push(detail)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result[result.length - 1] = {
|
||||||
|
...previous,
|
||||||
|
...Object.fromEntries(Object.entries(detail).filter((entry) => entry[1] !== undefined)),
|
||||||
|
text: `${typeof previous.text === "string" ? previous.text : ""}${typeof detail.text === "string" ? detail.text : ""}`,
|
||||||
|
signature: mergeDetailValue(previous.signature, detail.signature),
|
||||||
|
format: mergeDetailValue(previous.format, detail.format),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergeDetailValue = (previous: unknown, current: unknown) =>
|
||||||
|
previous || current || (previous !== undefined ? previous : current)
|
||||||
|
|
||||||
|
const conflictingReasoningTextDetails = (previous: Record<string, unknown>, current: Record<string, unknown>) =>
|
||||||
|
conflictingDetailValue(previous.id, current.id) ||
|
||||||
|
conflictingDetailValue(previous.index, current.index) ||
|
||||||
|
conflictingDetailValue(previous.format, current.format) ||
|
||||||
|
(Boolean(previous.signature) && Boolean(current.signature) && previous.signature !== current.signature)
|
||||||
|
|
||||||
|
const conflictingDetailValue = (previous: unknown, current: unknown) =>
|
||||||
|
previous !== undefined && previous !== null && current !== undefined && current !== null && previous !== current
|
||||||
|
|
||||||
|
const reasoningMetadata = (field: ParserState["reasoningField"], details?: ReadonlyArray<unknown>) => ({
|
||||||
|
openai: {
|
||||||
|
...(field ? { reasoningField: field } : {}),
|
||||||
|
...(details ? { reasoningDetails: details } : {}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const step = (state: ParserState, event: OpenAIChatEvent) =>
|
const step = (state: ParserState, event: OpenAIChatEvent) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const events: LLMEvent[] = []
|
const events: LLMEvent[] = []
|
||||||
@@ -409,25 +514,56 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
|
|||||||
const delta = choice?.delta
|
const delta = choice?.delta
|
||||||
const toolDeltas = delta?.tool_calls ?? []
|
const toolDeltas = delta?.tool_calls ?? []
|
||||||
let tools = state.tools
|
let tools = state.tools
|
||||||
|
let pendingTools = state.pendingTools
|
||||||
|
|
||||||
let lifecycle = state.lifecycle
|
let lifecycle = state.lifecycle
|
||||||
|
|
||||||
if (delta?.reasoning_content)
|
const reasoning = reasoningDelta(delta)
|
||||||
lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", delta.reasoning_content)
|
const reasoningField = state.reasoningField ?? (!state.lifecycle.text.has("text-0") ? reasoning?.field : undefined)
|
||||||
|
const detailDelta = Array.isArray(delta?.reasoning_details) ? delta.reasoning_details : undefined
|
||||||
|
if (detailDelta !== undefined) appendReasoningDetails(state.reasoningDetails, detailDelta)
|
||||||
|
const reasoningDetailsObserved = state.reasoningDetailsObserved || detailDelta !== undefined
|
||||||
|
const deltaMetadata = reasoningMetadata(reasoningField)
|
||||||
|
const text = detailDelta?.length ? (detailText(detailDelta) ?? reasoning?.text) : reasoning?.text
|
||||||
|
if (!state.lifecycle.text.has("text-0") && text !== undefined)
|
||||||
|
lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", text, deltaMetadata)
|
||||||
|
else if (
|
||||||
|
reasoningDetailsObserved &&
|
||||||
|
!lifecycle.reasoning.has("reasoning-0") &&
|
||||||
|
(Boolean(delta?.content) || toolDeltas.length > 0)
|
||||||
|
)
|
||||||
|
lifecycle = Lifecycle.reasoningStart(lifecycle, events, "reasoning-0", deltaMetadata)
|
||||||
|
const reasoningEmitted = state.reasoningEmitted || lifecycle.reasoning.has("reasoning-0")
|
||||||
|
|
||||||
if (delta?.content) {
|
if (delta?.content) {
|
||||||
lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0")
|
lifecycle = Lifecycle.reasoningEnd(
|
||||||
|
lifecycle,
|
||||||
|
events,
|
||||||
|
"reasoning-0",
|
||||||
|
reasoningMetadata(reasoningField, reasoningDetailsObserved ? state.reasoningDetails : undefined),
|
||||||
|
)
|
||||||
lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", delta.content)
|
lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", delta.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolDeltas.length) lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0")
|
|
||||||
|
|
||||||
for (const tool of toolDeltas) {
|
for (const tool of toolDeltas) {
|
||||||
|
const current = tools[tool.index]
|
||||||
|
const pending = pendingTools[tool.index]
|
||||||
|
const id = current?.id ?? pending?.id ?? (tool.id || undefined)
|
||||||
|
const name = current?.name ?? pending?.name ?? (tool.function?.name || undefined)
|
||||||
|
const text = `${pending?.input ?? ""}${tool.function?.arguments ?? ""}`
|
||||||
|
if (!current && (!id || !name)) {
|
||||||
|
pendingTools = { ...pendingTools, [tool.index]: { id: id || undefined, name: name || undefined, input: text } }
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (pending) {
|
||||||
|
pendingTools = { ...pendingTools }
|
||||||
|
delete pendingTools[tool.index]
|
||||||
|
}
|
||||||
const result = ToolStream.appendOrStart(
|
const result = ToolStream.appendOrStart(
|
||||||
ADAPTER,
|
ADAPTER,
|
||||||
tools,
|
tools,
|
||||||
tool.index,
|
tool.index,
|
||||||
{ id: tool.id ?? undefined, name: tool.function?.name ?? undefined, text: tool.function?.arguments ?? "" },
|
{ id: id || undefined, name: name || undefined, text },
|
||||||
"OpenAI Chat tool call delta is missing id or name",
|
"OpenAI Chat tool call delta is missing id or name",
|
||||||
)
|
)
|
||||||
if (ToolStream.isError(result)) return yield* result
|
if (ToolStream.isError(result)) return yield* result
|
||||||
@@ -436,8 +572,11 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
|
|||||||
events.push(...result.events)
|
events.push(...result.events)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (finishReason !== undefined && state.finishReason === undefined && Object.keys(pendingTools).length > 0)
|
||||||
|
return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat tool call delta is missing id or name")
|
||||||
|
|
||||||
// Finalize accumulated tool inputs eagerly when finish_reason arrives so
|
// Finalize accumulated tool inputs eagerly when finish_reason arrives so
|
||||||
// JSON parse failures fail the stream at the boundary rather than at halt.
|
// valid calls and malformed local calls settle independently.
|
||||||
const finished =
|
const finished =
|
||||||
finishReason !== undefined && state.finishReason === undefined && Object.keys(tools).length > 0
|
finishReason !== undefined && state.finishReason === undefined && Object.keys(tools).length > 0
|
||||||
? yield* ToolStream.finishAll(ADAPTER, tools)
|
? yield* ToolStream.finishAll(ADAPTER, tools)
|
||||||
@@ -446,10 +585,15 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tools: finished?.tools ?? tools,
|
tools: finished?.tools ?? tools,
|
||||||
|
pendingTools,
|
||||||
toolCallEvents: finished?.events ?? state.toolCallEvents,
|
toolCallEvents: finished?.events ?? state.toolCallEvents,
|
||||||
usage,
|
usage,
|
||||||
finishReason,
|
finishReason,
|
||||||
lifecycle,
|
lifecycle,
|
||||||
|
reasoningField,
|
||||||
|
reasoningDetails: state.reasoningDetails,
|
||||||
|
reasoningDetailsObserved,
|
||||||
|
reasoningEmitted,
|
||||||
},
|
},
|
||||||
events,
|
events,
|
||||||
] as const
|
] as const
|
||||||
@@ -459,7 +603,16 @@ const finishEvents = (state: ParserState): ReadonlyArray<LLMEvent> => {
|
|||||||
const events: LLMEvent[] = []
|
const events: LLMEvent[] = []
|
||||||
const hasToolCalls = state.toolCallEvents.length > 0
|
const hasToolCalls = state.toolCallEvents.length > 0
|
||||||
const reason = state.finishReason === "stop" && hasToolCalls ? "tool-calls" : state.finishReason
|
const reason = state.finishReason === "stop" && hasToolCalls ? "tool-calls" : state.finishReason
|
||||||
const lifecycle = state.toolCallEvents.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle
|
const metadata = reasoningMetadata(
|
||||||
|
state.reasoningField,
|
||||||
|
state.reasoningDetailsObserved ? state.reasoningDetails : undefined,
|
||||||
|
)
|
||||||
|
const started =
|
||||||
|
state.reasoningDetailsObserved && !state.reasoningEmitted
|
||||||
|
? Lifecycle.reasoningStart(state.lifecycle, events, "reasoning-0", reasoningMetadata(state.reasoningField))
|
||||||
|
: state.lifecycle
|
||||||
|
const ended = Lifecycle.reasoningEnd(started, events, "reasoning-0", metadata)
|
||||||
|
const lifecycle = state.toolCallEvents.length ? Lifecycle.stepStart(ended, events) : ended
|
||||||
events.push(...state.toolCallEvents)
|
events.push(...state.toolCallEvents)
|
||||||
if (reason) Lifecycle.finish(lifecycle, events, { reason, usage: state.usage })
|
if (reason) Lifecycle.finish(lifecycle, events, { reason, usage: state.usage })
|
||||||
return events
|
return events
|
||||||
@@ -482,7 +635,16 @@ export const protocol = Protocol.make({
|
|||||||
},
|
},
|
||||||
stream: {
|
stream: {
|
||||||
event: Protocol.jsonEvent(OpenAIChatEvent),
|
event: Protocol.jsonEvent(OpenAIChatEvent),
|
||||||
initial: () => ({ tools: ToolStream.empty<number>(), toolCallEvents: [], lifecycle: Lifecycle.initial() }),
|
initial: () => ({
|
||||||
|
tools: ToolStream.empty<number>(),
|
||||||
|
pendingTools: {},
|
||||||
|
toolCallEvents: [],
|
||||||
|
lifecycle: Lifecycle.initial(),
|
||||||
|
reasoningField: undefined,
|
||||||
|
reasoningDetails: [],
|
||||||
|
reasoningDetailsObserved: false,
|
||||||
|
reasoningEmitted: false,
|
||||||
|
}),
|
||||||
step,
|
step,
|
||||||
onHalt: finishEvents,
|
onHalt: finishEvents,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
import { Effect, Encoding, Schema } from "effect"
|
||||||
|
import { Headers, HttpClientRequest } from "effect/unstable/http"
|
||||||
|
import {
|
||||||
|
ImageModel,
|
||||||
|
GeneratedImage,
|
||||||
|
ImageResponse,
|
||||||
|
type ImageRequest,
|
||||||
|
type ImageModelDefaults,
|
||||||
|
type ImageRoute,
|
||||||
|
} from "../image"
|
||||||
|
import { Auth, type Definition as AuthDefinition } from "../route/auth"
|
||||||
|
import { InvalidProviderOutputReason, LLMError, Usage, mergeHttpOptions, mergeJsonRecords } from "../schema"
|
||||||
|
import { ProviderShared } from "./shared"
|
||||||
|
import { OpenAIImage } from "./utils/openai-image"
|
||||||
|
|
||||||
|
const ADAPTER = "openai-images"
|
||||||
|
export const DEFAULT_BASE_URL = "https://api.openai.com/v1"
|
||||||
|
export const PATH = "/images/generations"
|
||||||
|
|
||||||
|
export interface OpenAIImageOptions {
|
||||||
|
readonly quality?: "auto" | "low" | "medium" | "high"
|
||||||
|
readonly background?: "auto" | "opaque" | "transparent"
|
||||||
|
readonly moderation?: "auto" | "low"
|
||||||
|
readonly outputFormat?: "png" | "jpeg" | "webp"
|
||||||
|
readonly outputCompression?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const OpenAIImageBody = Schema.Struct({
|
||||||
|
model: Schema.String,
|
||||||
|
prompt: Schema.String,
|
||||||
|
n: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))),
|
||||||
|
size: Schema.optional(Schema.String),
|
||||||
|
quality: Schema.optional(Schema.Literals(["auto", "low", "medium", "high"])),
|
||||||
|
background: Schema.optional(Schema.Literals(["auto", "opaque", "transparent"])),
|
||||||
|
moderation: Schema.optional(Schema.Literals(["auto", "low"])),
|
||||||
|
output_format: Schema.optional(Schema.Literals(["png", "jpeg", "webp"])),
|
||||||
|
output_compression: Schema.optional(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 100 }))),
|
||||||
|
})
|
||||||
|
export type OpenAIImageBody = Schema.Schema.Type<typeof OpenAIImageBody>
|
||||||
|
|
||||||
|
const OpenAIImageResponse = Schema.Struct({
|
||||||
|
data: Schema.Array(
|
||||||
|
Schema.Struct({
|
||||||
|
b64_json: Schema.optional(Schema.String),
|
||||||
|
url: Schema.optional(Schema.String),
|
||||||
|
revised_prompt: Schema.optional(Schema.String),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
output_format: Schema.optional(Schema.String),
|
||||||
|
usage: Schema.optional(
|
||||||
|
Schema.Struct({
|
||||||
|
input_tokens: Schema.optional(Schema.Number),
|
||||||
|
output_tokens: Schema.optional(Schema.Number),
|
||||||
|
total_tokens: Schema.optional(Schema.Number),
|
||||||
|
input_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||||
|
output_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface ModelInput {
|
||||||
|
readonly id: string
|
||||||
|
readonly auth: AuthDefinition
|
||||||
|
readonly baseURL?: string
|
||||||
|
readonly headers?: Record<string, string>
|
||||||
|
readonly defaults?: ImageModelDefaults
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerOptions = (request: ImageRequest): OpenAIImageOptions => ({
|
||||||
|
...request.model.defaults?.providerOptions?.openai,
|
||||||
|
...request.providerOptions?.openai,
|
||||||
|
})
|
||||||
|
|
||||||
|
const body = (request: ImageRequest): OpenAIImageBody => {
|
||||||
|
const options = providerOptions(request)
|
||||||
|
return {
|
||||||
|
model: request.model.id,
|
||||||
|
prompt: request.prompt,
|
||||||
|
n: request.count,
|
||||||
|
size: request.size === undefined ? undefined : `${request.size.width}x${request.size.height}`,
|
||||||
|
quality: options.quality,
|
||||||
|
background: options.background,
|
||||||
|
moderation: options.moderation,
|
||||||
|
output_format: options.outputFormat,
|
||||||
|
output_compression: options.outputCompression,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalidOutput = (message: string) =>
|
||||||
|
new LLMError({
|
||||||
|
module: ADAPTER,
|
||||||
|
method: "generate",
|
||||||
|
reason: new InvalidProviderOutputReason({ message, route: ADAPTER }),
|
||||||
|
})
|
||||||
|
|
||||||
|
const applyQuery = (url: string, query: Record<string, string> | undefined) => {
|
||||||
|
if (!query) return url
|
||||||
|
const next = new URL(url)
|
||||||
|
Object.entries(query).forEach(([key, value]) => next.searchParams.set(key, value))
|
||||||
|
return next.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const PROTOCOL_BODY_FIELDS = new Set([
|
||||||
|
"model",
|
||||||
|
"prompt",
|
||||||
|
"n",
|
||||||
|
"size",
|
||||||
|
"quality",
|
||||||
|
"background",
|
||||||
|
"moderation",
|
||||||
|
"output_format",
|
||||||
|
"output_compression",
|
||||||
|
])
|
||||||
|
|
||||||
|
const bodyWithOverlay = Effect.fn("OpenAIImages.bodyWithOverlay")(function* (
|
||||||
|
imageBody: OpenAIImageBody,
|
||||||
|
overlay: Record<string, unknown> | undefined,
|
||||||
|
) {
|
||||||
|
if (!overlay) return imageBody
|
||||||
|
const reserved = Object.keys(overlay).filter((key) => PROTOCOL_BODY_FIELDS.has(key))
|
||||||
|
if (reserved.length > 0)
|
||||||
|
return yield* ProviderShared.invalidRequest(
|
||||||
|
`http.body cannot overlay protocol-owned field(s): ${reserved.join(", ")}`,
|
||||||
|
)
|
||||||
|
return mergeJsonRecords(imageBody, overlay) ?? imageBody
|
||||||
|
})
|
||||||
|
|
||||||
|
export const model = (input: ModelInput) => {
|
||||||
|
const route: ImageRoute = {
|
||||||
|
id: ADAPTER,
|
||||||
|
generate: Effect.fn("OpenAIImages.generate")(function* (request: ImageRequest, execute) {
|
||||||
|
if (request.aspectRatio !== undefined)
|
||||||
|
return yield* ProviderShared.invalidRequest("OpenAI Images does not support the common aspectRatio option")
|
||||||
|
if (request.seed !== undefined)
|
||||||
|
return yield* ProviderShared.invalidRequest("OpenAI Images does not support the common seed option")
|
||||||
|
|
||||||
|
const requestBody = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(OpenAIImageBody))(body(request))
|
||||||
|
const http = mergeHttpOptions(request.model.defaults?.http, request.http)
|
||||||
|
const overlaidBody = yield* bodyWithOverlay(requestBody, http?.body)
|
||||||
|
const text = ProviderShared.encodeJson(overlaidBody)
|
||||||
|
const url = applyQuery(`${(input.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "")}${PATH}`, http?.query)
|
||||||
|
const headers = yield* Auth.toEffect(input.auth)({
|
||||||
|
request,
|
||||||
|
method: "POST",
|
||||||
|
url,
|
||||||
|
body: text,
|
||||||
|
headers: Headers.fromInput({ ...input.headers, ...http?.headers }),
|
||||||
|
})
|
||||||
|
const response = yield* execute(
|
||||||
|
HttpClientRequest.post(url).pipe(
|
||||||
|
HttpClientRequest.setHeaders(headers),
|
||||||
|
HttpClientRequest.bodyText(text, "application/json"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
const payload = yield* response.json.pipe(
|
||||||
|
Effect.mapError(() => invalidOutput("Failed to read the OpenAI Images response")),
|
||||||
|
)
|
||||||
|
const decoded = yield* Schema.decodeUnknownEffect(OpenAIImageResponse)(payload).pipe(
|
||||||
|
Effect.mapError(() => invalidOutput("OpenAI Images returned an invalid response")),
|
||||||
|
)
|
||||||
|
const format = decoded.output_format ?? providerOptions(request).outputFormat ?? "png"
|
||||||
|
const images = yield* Effect.forEach(decoded.data, (item, index) => {
|
||||||
|
if (item.b64_json)
|
||||||
|
return Effect.fromResult(Encoding.decodeBase64(item.b64_json)).pipe(
|
||||||
|
Effect.mapError(() => invalidOutput(`OpenAI Images result ${index} contains invalid base64 data`)),
|
||||||
|
Effect.map(
|
||||||
|
(data) =>
|
||||||
|
new GeneratedImage({
|
||||||
|
mediaType: `image/${format}`,
|
||||||
|
data,
|
||||||
|
providerMetadata:
|
||||||
|
item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (item.url)
|
||||||
|
return Effect.succeed(
|
||||||
|
new GeneratedImage({
|
||||||
|
mediaType: `image/${format}`,
|
||||||
|
data: item.url,
|
||||||
|
providerMetadata:
|
||||||
|
item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
return Effect.fail(invalidOutput(`OpenAI Images result ${index} has neither image data nor a URL`))
|
||||||
|
})
|
||||||
|
if (images.length === 0) return yield* invalidOutput("OpenAI Images returned no images")
|
||||||
|
return new ImageResponse({
|
||||||
|
images,
|
||||||
|
usage:
|
||||||
|
decoded.usage === undefined
|
||||||
|
? undefined
|
||||||
|
: new Usage({
|
||||||
|
inputTokens: decoded.usage.input_tokens,
|
||||||
|
outputTokens: decoded.usage.output_tokens,
|
||||||
|
totalTokens: decoded.usage.total_tokens,
|
||||||
|
providerMetadata: { openai: decoded.usage },
|
||||||
|
}),
|
||||||
|
providerMetadata: { openai: { outputFormat: format } },
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
return ImageModel.make({ id: input.id, provider: "openai", route, defaults: input.defaults })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OpenAIImages = {
|
||||||
|
model,
|
||||||
|
} as const
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Effect, Schema } from "effect"
|
import { Effect, Encoding, Schema } from "effect"
|
||||||
import { Route } from "../route/client"
|
import { Route } from "../route/client"
|
||||||
import { Auth } from "../route/auth"
|
import { Auth } from "../route/auth"
|
||||||
import { Endpoint } from "../route/endpoint"
|
import { Endpoint } from "../route/endpoint"
|
||||||
@@ -25,6 +25,7 @@ import { OpenAIOptions } from "./utils/openai-options"
|
|||||||
import { Lifecycle } from "./utils/lifecycle"
|
import { Lifecycle } from "./utils/lifecycle"
|
||||||
import { ToolSchemaProjection } from "./utils/tool-schema"
|
import { ToolSchemaProjection } from "./utils/tool-schema"
|
||||||
import { ToolStream } from "./utils/tool-stream"
|
import { ToolStream } from "./utils/tool-stream"
|
||||||
|
import { OpenAIImage } from "./utils/openai-image"
|
||||||
|
|
||||||
const ADAPTER = "openai-responses"
|
const ADAPTER = "openai-responses"
|
||||||
export const DEFAULT_BASE_URL = "https://api.openai.com/v1"
|
export const DEFAULT_BASE_URL = "https://api.openai.com/v1"
|
||||||
@@ -113,11 +114,24 @@ const OpenAIResponsesTool = Schema.Struct({
|
|||||||
parameters: JsonObject,
|
parameters: JsonObject,
|
||||||
strict: Schema.optional(Schema.Boolean),
|
strict: Schema.optional(Schema.Boolean),
|
||||||
})
|
})
|
||||||
type OpenAIResponsesTool = Schema.Schema.Type<typeof OpenAIResponsesTool>
|
const OpenAIResponsesImageGenerationTool = Schema.Struct({
|
||||||
|
type: Schema.tag("image_generation"),
|
||||||
|
action: Schema.optional(Schema.Literals(["auto", "generate", "edit"])),
|
||||||
|
background: Schema.optional(Schema.Literals(["auto", "opaque", "transparent"])),
|
||||||
|
input_fidelity: Schema.optional(Schema.Literals(["low", "high"])),
|
||||||
|
output_compression: Schema.optional(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 100 }))),
|
||||||
|
output_format: Schema.optional(Schema.Literals(["png", "jpeg", "webp"])),
|
||||||
|
partial_images: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
|
||||||
|
quality: Schema.optional(Schema.Literals(["auto", "low", "medium", "high"])),
|
||||||
|
size: Schema.optional(OpenAIImage.Size),
|
||||||
|
})
|
||||||
|
const OpenAIResponsesTools = Schema.Union([OpenAIResponsesTool, OpenAIResponsesImageGenerationTool])
|
||||||
|
type OpenAIResponsesTool = Schema.Schema.Type<typeof OpenAIResponsesTools>
|
||||||
|
|
||||||
const OpenAIResponsesToolChoice = Schema.Union([
|
const OpenAIResponsesToolChoice = Schema.Union([
|
||||||
Schema.Literals(["auto", "none", "required"]),
|
Schema.Literals(["auto", "none", "required"]),
|
||||||
Schema.Struct({ type: Schema.tag("function"), name: Schema.String }),
|
Schema.Struct({ type: Schema.tag("function"), name: Schema.String }),
|
||||||
|
Schema.Struct({ type: Schema.tag("image_generation") }),
|
||||||
])
|
])
|
||||||
|
|
||||||
// Fields shared between the HTTP body and the WebSocket `response.create`
|
// Fields shared between the HTTP body and the WebSocket `response.create`
|
||||||
@@ -128,7 +142,7 @@ const OpenAIResponsesCoreFields = {
|
|||||||
model: Schema.String,
|
model: Schema.String,
|
||||||
input: Schema.Array(OpenAIResponsesInputItem),
|
input: Schema.Array(OpenAIResponsesInputItem),
|
||||||
instructions: Schema.optional(Schema.String),
|
instructions: Schema.optional(Schema.String),
|
||||||
tools: optionalArray(OpenAIResponsesTool),
|
tools: optionalArray(OpenAIResponsesTools),
|
||||||
tool_choice: Schema.optional(OpenAIResponsesToolChoice),
|
tool_choice: Schema.optional(OpenAIResponsesToolChoice),
|
||||||
store: Schema.optional(Schema.Boolean),
|
store: Schema.optional(Schema.Boolean),
|
||||||
service_tier: Schema.optional(OpenAIOptions.OpenAIServiceTier),
|
service_tier: Schema.optional(OpenAIOptions.OpenAIServiceTier),
|
||||||
@@ -194,6 +208,8 @@ const OpenAIResponsesStreamItem = Schema.Struct({
|
|||||||
outputs: Schema.optional(Schema.Unknown),
|
outputs: Schema.optional(Schema.Unknown),
|
||||||
server_label: Schema.optional(Schema.String),
|
server_label: Schema.optional(Schema.String),
|
||||||
output: Schema.optional(Schema.Unknown),
|
output: Schema.optional(Schema.Unknown),
|
||||||
|
result: Schema.optional(Schema.String),
|
||||||
|
output_format: Schema.optional(Schema.Literals(["png", "jpeg", "webp"])),
|
||||||
error: Schema.optional(Schema.Unknown),
|
error: Schema.optional(Schema.Unknown),
|
||||||
encrypted_content: optionalNull(Schema.String),
|
encrypted_content: optionalNull(Schema.String),
|
||||||
})
|
})
|
||||||
@@ -258,21 +274,41 @@ const invalid = ProviderShared.invalidRequest
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Request Lowering
|
// Request Lowering
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
const lowerTool = (tool: ToolDefinition, inputSchema: JsonSchema): OpenAIResponsesTool => ({
|
const nativeImageToolInput = (tool: ToolDefinition) => {
|
||||||
type: "function",
|
const native = tool.native?.openai
|
||||||
name: tool.name,
|
return ProviderShared.isRecord(native) && native.type === "image_generation" ? native : undefined
|
||||||
description: tool.description,
|
}
|
||||||
parameters: ToolSchemaProjection.openAI(inputSchema),
|
|
||||||
// TODO: Read this from OpenAI-specific tool options so direct LLM callers can opt into strict schemas.
|
const nativeImageTool = (tool: ToolDefinition) => {
|
||||||
strict: false,
|
const native = nativeImageToolInput(tool)
|
||||||
|
return Schema.is(OpenAIResponsesImageGenerationTool)(native) ? native : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowerTool = Effect.fn("OpenAIResponses.lowerTool")(function* (tool: ToolDefinition, inputSchema: JsonSchema) {
|
||||||
|
const native = nativeImageToolInput(tool)
|
||||||
|
if (native !== undefined) {
|
||||||
|
if (Schema.is(OpenAIResponsesImageGenerationTool)(native)) return native
|
||||||
|
return yield* invalid("OpenAI Responses image generation tool options are invalid")
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: "function" as const,
|
||||||
|
name: tool.name,
|
||||||
|
description: tool.description,
|
||||||
|
parameters: ToolSchemaProjection.openAI(inputSchema),
|
||||||
|
// TODO: Read this from OpenAI-specific tool options so direct LLM callers can opt into strict schemas.
|
||||||
|
strict: false,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const lowerToolChoice = (toolChoice: NonNullable<LLMRequest["toolChoice"]>) =>
|
const lowerToolChoice = (toolChoice: NonNullable<LLMRequest["toolChoice"]>, tools: ReadonlyArray<ToolDefinition>) =>
|
||||||
ProviderShared.matchToolChoice("OpenAI Responses", toolChoice, {
|
ProviderShared.matchToolChoice("OpenAI Responses", toolChoice, {
|
||||||
auto: () => "auto" as const,
|
auto: () => "auto" as const,
|
||||||
none: () => "none" as const,
|
none: () => "none" as const,
|
||||||
required: () => "required" as const,
|
required: () => "required" as const,
|
||||||
tool: (name) => ({ type: "function" as const, name }),
|
tool: (name) =>
|
||||||
|
tools.some((tool) => tool.name === name && nativeImageTool(tool) !== undefined)
|
||||||
|
? ({ type: "image_generation" } as const)
|
||||||
|
: { type: "function" as const, name },
|
||||||
})
|
})
|
||||||
|
|
||||||
const lowerToolCall = (part: ToolCallPart): OpenAIResponsesInputItem => ({
|
const lowerToolCall = (part: ToolCallPart): OpenAIResponsesInputItem => ({
|
||||||
@@ -420,6 +456,13 @@ const lowerMessages = Effect.fn("OpenAIResponses.lowerMessages")(function* (requ
|
|||||||
const itemID = hostedToolItemID(part)
|
const itemID = hostedToolItemID(part)
|
||||||
if (store !== false && itemID && !hostedToolReferences.has(itemID))
|
if (store !== false && itemID && !hostedToolReferences.has(itemID))
|
||||||
input.push({ type: "item_reference", id: itemID })
|
input.push({ type: "item_reference", id: itemID })
|
||||||
|
if (store === false && part.name === "image_generation" && part.result.type === "content") {
|
||||||
|
const content: ReadonlyArray<ToolContent> = part.result.value
|
||||||
|
input.push({
|
||||||
|
role: "user",
|
||||||
|
content: yield* Effect.forEach(content, lowerToolResultContentItem),
|
||||||
|
})
|
||||||
|
}
|
||||||
if (itemID) hostedToolReferences.add(itemID)
|
if (itemID) hostedToolReferences.add(itemID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -485,10 +528,10 @@ const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request:
|
|||||||
tools:
|
tools:
|
||||||
request.tools.length === 0
|
request.tools.length === 0
|
||||||
? undefined
|
? undefined
|
||||||
: request.tools.map((tool) =>
|
: yield* Effect.forEach(request.tools, (tool) =>
|
||||||
lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility)),
|
lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility)),
|
||||||
),
|
),
|
||||||
tool_choice: request.toolChoice ? yield* lowerToolChoice(request.toolChoice) : undefined,
|
tool_choice: request.toolChoice ? yield* lowerToolChoice(request.toolChoice, request.tools) : undefined,
|
||||||
stream: true as const,
|
stream: true as const,
|
||||||
max_output_tokens: generation?.maxTokens,
|
max_output_tokens: generation?.maxTokens,
|
||||||
temperature: generation?.temperature,
|
temperature: generation?.temperature,
|
||||||
@@ -574,14 +617,29 @@ const isReasoningItem = (
|
|||||||
|
|
||||||
// Round-trip the full item as the structured result so consumers can extract
|
// Round-trip the full item as the structured result so consumers can extract
|
||||||
// outputs / sources / status without re-decoding.
|
// outputs / sources / status without re-decoding.
|
||||||
const hostedToolResult = (item: OpenAIResponsesStreamItem) => {
|
const hostedToolResult = Effect.fn("OpenAIResponses.hostedToolResult")(function* (item: OpenAIResponsesStreamItem) {
|
||||||
const isError = typeof item.error !== "undefined" && item.error !== null
|
const isError = typeof item.error !== "undefined" && item.error !== null
|
||||||
|
if (item.type === "image_generation_call" && item.result) {
|
||||||
|
yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(
|
||||||
|
Effect.mapError(() => ProviderShared.eventError(ADAPTER, "OpenAI Responses returned invalid image base64")),
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
type: "content" as const,
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: "file" as const,
|
||||||
|
uri: `data:image/${item.output_format ?? "png"};base64,${item.result}`,
|
||||||
|
mime: `image/${item.output_format ?? "png"}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
return isError ? { type: "error" as const, value: item.error } : { type: "json" as const, value: item }
|
return isError ? { type: "error" as const, value: item.error } : { type: "json" as const, value: item }
|
||||||
}
|
})
|
||||||
|
|
||||||
const hostedToolEvents = (
|
const hostedToolEvents = Effect.fn("OpenAIResponses.hostedToolEvents")(function* (
|
||||||
item: OpenAIResponsesStreamItem & { type: HostedToolType; id: string },
|
item: OpenAIResponsesStreamItem & { type: HostedToolType; id: string },
|
||||||
): ReadonlyArray<LLMEvent> => {
|
) {
|
||||||
const tool = HOSTED_TOOLS[item.type]
|
const tool = HOSTED_TOOLS[item.type]
|
||||||
const providerMetadata = openaiMetadata({ itemId: item.id })
|
const providerMetadata = openaiMetadata({ itemId: item.id })
|
||||||
return [
|
return [
|
||||||
@@ -595,12 +653,12 @@ const hostedToolEvents = (
|
|||||||
LLMEvent.toolResult({
|
LLMEvent.toolResult({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
name: tool.name,
|
name: tool.name,
|
||||||
result: hostedToolResult(item),
|
result: yield* hostedToolResult(item),
|
||||||
providerExecuted: true,
|
providerExecuted: true,
|
||||||
providerMetadata,
|
providerMetadata,
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
}
|
})
|
||||||
|
|
||||||
type StepResult = readonly [ParserState, ReadonlyArray<LLMEvent>]
|
type StepResult = readonly [ParserState, ReadonlyArray<LLMEvent>]
|
||||||
|
|
||||||
@@ -835,7 +893,9 @@ const onOutputItemDone = Effect.fn("OpenAIResponses.onOutputItemDone")(function*
|
|||||||
{
|
{
|
||||||
...state,
|
...state,
|
||||||
lifecycle,
|
lifecycle,
|
||||||
hasFunctionCall: resultEvents.some(LLMEvent.is.toolCall) ? true : state.hasFunctionCall,
|
hasFunctionCall:
|
||||||
|
resultEvents.some((event) => LLMEvent.is.toolCall(event) || LLMEvent.is.toolInputError(event)) ||
|
||||||
|
state.hasFunctionCall,
|
||||||
tools: result.tools,
|
tools: result.tools,
|
||||||
},
|
},
|
||||||
events,
|
events,
|
||||||
@@ -845,7 +905,7 @@ const onOutputItemDone = Effect.fn("OpenAIResponses.onOutputItemDone")(function*
|
|||||||
if (isHostedToolItem(item)) {
|
if (isHostedToolItem(item)) {
|
||||||
const events: LLMEvent[] = []
|
const events: LLMEvent[] = []
|
||||||
const lifecycle = Lifecycle.stepStart(state.lifecycle, events)
|
const lifecycle = Lifecycle.stepStart(state.lifecycle, events)
|
||||||
events.push(...hostedToolEvents(item))
|
events.push(...(yield* hostedToolEvents(item)))
|
||||||
return [{ ...state, lifecycle }, events] satisfies StepResult
|
return [{ ...state, lifecycle }, events] satisfies StepResult
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -878,18 +938,10 @@ const onOutputItemDone = Effect.fn("OpenAIResponses.onOutputItemDone")(function*
|
|||||||
return [state, NO_EVENTS] satisfies StepResult
|
return [state, NO_EVENTS] satisfies StepResult
|
||||||
})
|
})
|
||||||
|
|
||||||
const onResponseFinish = Effect.fn("OpenAIResponses.onResponseFinish")(function* (
|
const onResponseFinish = (state: ParserState, event: OpenAIResponsesEvent): StepResult => {
|
||||||
state: ParserState,
|
const events: LLMEvent[] = []
|
||||||
event: OpenAIResponsesEvent,
|
|
||||||
) {
|
|
||||||
const pending =
|
|
||||||
event.type === "response.completed"
|
|
||||||
? yield* ToolStream.finishAll(ADAPTER, state.tools)
|
|
||||||
: { tools: state.tools, events: NO_EVENTS }
|
|
||||||
const events = [...pending.events]
|
|
||||||
const hasFunctionCall = state.hasFunctionCall || pending.events.some(LLMEvent.is.toolCall)
|
|
||||||
const lifecycle = Lifecycle.finish(state.lifecycle, events, {
|
const lifecycle = Lifecycle.finish(state.lifecycle, events, {
|
||||||
reason: mapFinishReason(event, hasFunctionCall),
|
reason: mapFinishReason(event, state.hasFunctionCall),
|
||||||
usage: mapUsage(event.response?.usage),
|
usage: mapUsage(event.response?.usage),
|
||||||
providerMetadata:
|
providerMetadata:
|
||||||
event.response?.id || event.response?.service_tier
|
event.response?.id || event.response?.service_tier
|
||||||
@@ -899,8 +951,8 @@ const onResponseFinish = Effect.fn("OpenAIResponses.onResponseFinish")(function*
|
|||||||
})
|
})
|
||||||
: undefined,
|
: undefined,
|
||||||
})
|
})
|
||||||
return [{ ...state, tools: pending.tools, hasFunctionCall, lifecycle }, events] satisfies StepResult
|
return [{ ...state, lifecycle }, events]
|
||||||
})
|
}
|
||||||
|
|
||||||
// Build a single human-readable message from whatever the provider supplied.
|
// Build a single human-readable message from whatever the provider supplied.
|
||||||
// When both code and message are present, prefix the code so consumers see
|
// When both code and message are present, prefix the code so consumers see
|
||||||
@@ -947,9 +999,8 @@ const step = (state: ParserState, event: OpenAIResponsesEvent) => {
|
|||||||
if (event.type === "response.output_item.added") return Effect.succeed(onOutputItemAdded(state, event))
|
if (event.type === "response.output_item.added") return Effect.succeed(onOutputItemAdded(state, event))
|
||||||
if (event.type === "response.function_call_arguments.delta") return onFunctionCallArgumentsDelta(state, event)
|
if (event.type === "response.function_call_arguments.delta") return onFunctionCallArgumentsDelta(state, event)
|
||||||
if (event.type === "response.output_item.done") return onOutputItemDone(state, event)
|
if (event.type === "response.output_item.done") return onOutputItemDone(state, event)
|
||||||
if (event.type === "response.incomplete" && Object.keys(state.tools).length > 0)
|
if (event.type === "response.completed" || event.type === "response.incomplete")
|
||||||
return ProviderShared.eventError(ADAPTER, "OpenAI Responses response incomplete with pending tool calls")
|
return Effect.succeed(onResponseFinish(state, event))
|
||||||
if (event.type === "response.completed" || event.type === "response.incomplete") return onResponseFinish(state, event)
|
|
||||||
if (event.type === "response.failed") return providerError(event, "OpenAI Responses response failed")
|
if (event.type === "response.failed") return providerError(event, "OpenAI Responses response failed")
|
||||||
if (event.type === "error") return providerError(event, "OpenAI Responses stream error")
|
if (event.type === "error") return providerError(event, "OpenAI Responses stream error")
|
||||||
return Effect.succeed<StepResult>([state, NO_EVENTS])
|
return Effect.succeed<StepResult>([state, NO_EVENTS])
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const reasoningDelta = (
|
|||||||
providerMetadata?: ProviderMetadata,
|
providerMetadata?: ProviderMetadata,
|
||||||
): State => {
|
): State => {
|
||||||
const started = reasoningStart(state, events, id, providerMetadata)
|
const started = reasoningStart(state, events, id, providerMetadata)
|
||||||
events.push(LLMEvent.reasoningDelta({ id, text }))
|
events.push(LLMEvent.reasoningDelta({ id, text, providerMetadata }))
|
||||||
return started
|
return started
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Schema } from "effect"
|
||||||
|
|
||||||
|
const dimensions = (value: string) => {
|
||||||
|
const match = /^(\d+)x(\d+)$/.exec(value)
|
||||||
|
if (!match) return undefined
|
||||||
|
return { width: Number(match[1]), height: Number(match[2]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Size = Schema.String.check(
|
||||||
|
Schema.makeFilter((value) => {
|
||||||
|
if (value === "auto") return undefined
|
||||||
|
const parsed = dimensions(value)
|
||||||
|
if (!parsed) return "image size must be `auto` or `{width}x{height}`"
|
||||||
|
return parsed.width > 0 && parsed.height > 0 ? undefined : "image dimensions must be positive integers"
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
export const OpenAIImage = {
|
||||||
|
Size,
|
||||||
|
} as const
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { LLMError, LLMEvent, type ProviderMetadata, type ToolCall } from "../../schema"
|
import { LLMError, LLMEvent, type ProviderMetadata, type ToolCall, type ToolInputError } from "../../schema"
|
||||||
import { eventError, parseToolInput, type ToolAccumulator } from "../shared"
|
import { eventError, parseToolInput, type ToolAccumulator } from "../shared"
|
||||||
|
|
||||||
type StreamKey = string | number
|
type StreamKey = string | number
|
||||||
@@ -53,6 +53,7 @@ const inputStart = (tool: PendingTool) =>
|
|||||||
LLMEvent.toolInputStart({
|
LLMEvent.toolInputStart({
|
||||||
id: tool.id,
|
id: tool.id,
|
||||||
name: tool.name,
|
name: tool.name,
|
||||||
|
providerExecuted: tool.providerExecuted ? true : undefined,
|
||||||
providerMetadata: tool.providerMetadata,
|
providerMetadata: tool.providerMetadata,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -63,19 +64,36 @@ const inputDelta = (tool: PendingTool, text: string) =>
|
|||||||
text,
|
text,
|
||||||
})
|
})
|
||||||
|
|
||||||
const toolCall = (route: string, tool: PendingTool, inputOverride?: string) =>
|
const toolCall = (route: string, tool: PendingTool, inputOverride?: string) => {
|
||||||
parseToolInput(route, tool.name, inputOverride ?? tool.input).pipe(
|
const raw = inputOverride ?? tool.input
|
||||||
Effect.map(
|
return parseToolInput(route, tool.name, raw).pipe(
|
||||||
(input): ToolCall =>
|
Effect.map((input): ToolCall | ToolInputError =>
|
||||||
LLMEvent.toolCall({
|
LLMEvent.toolCall({
|
||||||
id: tool.id,
|
id: tool.id,
|
||||||
name: tool.name,
|
name: tool.name,
|
||||||
input,
|
input,
|
||||||
providerExecuted: tool.providerExecuted ? true : undefined,
|
providerExecuted: tool.providerExecuted ? true : undefined,
|
||||||
providerMetadata: tool.providerMetadata,
|
providerMetadata: tool.providerMetadata,
|
||||||
}),
|
}),
|
||||||
|
),
|
||||||
|
Effect.catch((error) =>
|
||||||
|
tool.providerExecuted
|
||||||
|
? Effect.fail(error)
|
||||||
|
: Effect.succeed(
|
||||||
|
LLMEvent.toolInputError({
|
||||||
|
id: tool.id,
|
||||||
|
name: tool.name,
|
||||||
|
raw,
|
||||||
|
}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const finishEvents = (tool: PendingTool, event: ToolCall | ToolInputError): ReadonlyArray<LLMEvent> =>
|
||||||
|
event.type === "tool-input-error"
|
||||||
|
? [event]
|
||||||
|
: [LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }), event]
|
||||||
|
|
||||||
/** Store the updated tool and produce the optional public delta event. */
|
/** Store the updated tool and produce the optional public delta event. */
|
||||||
const appendTool = <K extends StreamKey>(
|
const appendTool = <K extends StreamKey>(
|
||||||
@@ -122,8 +140,8 @@ export const appendOrStart = <K extends StreamKey>(
|
|||||||
missingToolMessage: string,
|
missingToolMessage: string,
|
||||||
): AppendOutcome<K> | LLMError => {
|
): AppendOutcome<K> | LLMError => {
|
||||||
const current = tools[key]
|
const current = tools[key]
|
||||||
const id = delta.id ?? current?.id
|
const id = current?.id ?? delta.id
|
||||||
const name = delta.name ?? current?.name
|
const name = current?.name ?? delta.name
|
||||||
if (!id || !name) return eventError(route, missingToolMessage)
|
if (!id || !name) return eventError(route, missingToolMessage)
|
||||||
|
|
||||||
const tool = {
|
const tool = {
|
||||||
@@ -158,8 +176,9 @@ export const appendExisting = <K extends StreamKey>(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Finalize one pending tool call: parse the accumulated raw JSON, remove it
|
* Finalize one pending tool call: parse the accumulated raw JSON, remove it
|
||||||
* from state, and return the optional public `tool-call` event. Missing keys are
|
* from state, and return either a call or a non-executable local input error.
|
||||||
* a no-op because some providers emit stop events for non-tool content blocks.
|
* Missing keys are a no-op because some providers emit stop events for
|
||||||
|
* non-tool content blocks.
|
||||||
*/
|
*/
|
||||||
export const finish = <K extends StreamKey>(route: string, tools: State<K>, key: K) =>
|
export const finish = <K extends StreamKey>(route: string, tools: State<K>, key: K) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
@@ -167,10 +186,7 @@ export const finish = <K extends StreamKey>(route: string, tools: State<K>, key:
|
|||||||
if (!tool) return { tools }
|
if (!tool) return { tools }
|
||||||
return {
|
return {
|
||||||
tools: withoutTool(tools, key),
|
tools: withoutTool(tools, key),
|
||||||
events: [
|
events: finishEvents(tool, yield* toolCall(route, tool)),
|
||||||
LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }),
|
|
||||||
yield* toolCall(route, tool),
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -185,17 +201,14 @@ export const finishWithInput = <K extends StreamKey>(route: string, tools: State
|
|||||||
if (!tool) return { tools }
|
if (!tool) return { tools }
|
||||||
return {
|
return {
|
||||||
tools: withoutTool(tools, key),
|
tools: withoutTool(tools, key),
|
||||||
events: [
|
events: finishEvents(tool, yield* toolCall(route, tool, input)),
|
||||||
LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }),
|
|
||||||
yield* toolCall(route, tool, input),
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finalize every pending tool call at once. OpenAI Chat has this shape: it does
|
* Finalize every pending tool call at once. OpenAI Chat has this shape: it does
|
||||||
* not emit per-tool stop events, so all accumulated calls finish when the choice
|
* not emit per-tool stop events, so all accumulated calls finish independently
|
||||||
* receives a terminal `finish_reason`.
|
* when the choice receives a terminal `finish_reason`.
|
||||||
*/
|
*/
|
||||||
export const finishAll = <K extends StreamKey>(route: string, tools: State<K>) =>
|
export const finishAll = <K extends StreamKey>(route: string, tools: State<K>) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
@@ -205,12 +218,7 @@ export const finishAll = <K extends StreamKey>(route: string, tools: State<K>) =
|
|||||||
return {
|
return {
|
||||||
tools: empty<K>(),
|
tools: empty<K>(),
|
||||||
events: yield* Effect.forEach(pending, (tool) =>
|
events: yield* Effect.forEach(pending, (tool) =>
|
||||||
toolCall(route, tool).pipe(
|
toolCall(route, tool).pipe(Effect.map((event) => finishEvents(tool, event))),
|
||||||
Effect.map((call) => [
|
|
||||||
LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }),
|
|
||||||
call,
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
).pipe(Effect.map((events) => events.flat())),
|
).pipe(Effect.map((events) => events.flat())),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,13 +16,17 @@ import {
|
|||||||
|
|
||||||
const patterns = [
|
const patterns = [
|
||||||
/prompt is too long/i,
|
/prompt is too long/i,
|
||||||
|
/request_too_large/i,
|
||||||
/input is too long for requested model/i,
|
/input is too long for requested model/i,
|
||||||
/exceeds the context window/i,
|
/exceeds the context window/i,
|
||||||
|
/exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\d,]+ tokens?|\s*\([\d,]+\))/i,
|
||||||
/input token count.*exceeds the maximum/i,
|
/input token count.*exceeds the maximum/i,
|
||||||
/tokens in request more than max tokens allowed/i,
|
/tokens in request more than max tokens allowed/i,
|
||||||
/maximum prompt length is \d+/i,
|
/maximum prompt length is \d+/i,
|
||||||
/reduce the length of the messages/i,
|
/reduce the length of the messages/i,
|
||||||
/maximum context length is \d+ tokens/i,
|
/maximum context length is \d+ tokens/i,
|
||||||
|
/exceeds (?:the )?maximum allowed input length of [\d,]+ tokens?/i,
|
||||||
|
/input \(\d+ tokens\) is longer than the model'?s context length \(\d+ tokens\)/i,
|
||||||
/exceeds the limit of \d+/i,
|
/exceeds the limit of \d+/i,
|
||||||
/exceeds the available context size/i,
|
/exceeds the available context size/i,
|
||||||
/greater than the context length/i,
|
/greater than the context length/i,
|
||||||
@@ -34,11 +38,17 @@ const patterns = [
|
|||||||
/input length.*exceeds.*context length/i,
|
/input length.*exceeds.*context length/i,
|
||||||
/prompt too long; exceeded (?:max )?context length/i,
|
/prompt too long; exceeded (?:max )?context length/i,
|
||||||
/too large for model with \d+ maximum context length/i,
|
/too large for model with \d+ maximum context length/i,
|
||||||
|
/prompt has [\d,]+ tokens?, but the configured context size is [\d,]+ tokens?/i,
|
||||||
/model_context_window_exceeded/i,
|
/model_context_window_exceeded/i,
|
||||||
|
/too many tokens/i,
|
||||||
|
/token limit exceeded/i,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const exclusions = [/^(throttling error|service unavailable):/i, /rate limit/i, /too many requests/i]
|
||||||
|
|
||||||
export const isContextOverflow = (message: string) =>
|
export const isContextOverflow = (message: string) =>
|
||||||
patterns.some((pattern) => pattern.test(message)) || /^4(00|13)\s*(status code)?\s*\(no body\)/i.test(message)
|
!exclusions.some((pattern) => pattern.test(message)) &&
|
||||||
|
(patterns.some((pattern) => pattern.test(message)) || /^4(00|13)\s*(status code)?\s*\(no body\)/i.test(message))
|
||||||
|
|
||||||
export const isContextOverflowFailure = (failure: unknown) =>
|
export const isContextOverflowFailure = (failure: unknown) =>
|
||||||
failure instanceof LLMError
|
failure instanceof LLMError
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import type { ProviderPackage } from "../provider-package"
|
||||||
|
import { OpenAICompatibleChat } from "../protocols/openai-compatible-chat"
|
||||||
|
import type { RouteDefaultsInput } from "../route/client"
|
||||||
|
import { ProviderID, type ModelID, type ProviderOptions } from "../schema"
|
||||||
|
import { GoogleVertexShared } from "./google-vertex-shared"
|
||||||
|
|
||||||
|
export const id = ProviderID.make("google-vertex")
|
||||||
|
|
||||||
|
export type Config = RouteDefaultsInput &
|
||||||
|
GoogleVertexShared.OAuthOptions & {
|
||||||
|
readonly baseURL?: string
|
||||||
|
readonly location?: string
|
||||||
|
readonly project?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Settings extends ProviderPackage.Settings {
|
||||||
|
readonly accessToken?: string
|
||||||
|
readonly apiKey?: never
|
||||||
|
readonly baseURL?: string
|
||||||
|
readonly location?: string
|
||||||
|
readonly project?: string
|
||||||
|
readonly providerOptions?: ProviderOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
const route = OpenAICompatibleChat.route.with({
|
||||||
|
id: "google-vertex-chat",
|
||||||
|
provider: id,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const routes = [route]
|
||||||
|
|
||||||
|
const configuredRoute = (input: Config) => {
|
||||||
|
if ("apiKey" in input && input.apiKey !== undefined) throw new Error("Google Vertex Chat does not support API keys")
|
||||||
|
const {
|
||||||
|
accessToken: _accessToken,
|
||||||
|
auth: _auth,
|
||||||
|
baseURL,
|
||||||
|
location: inputLocation,
|
||||||
|
project: inputProject,
|
||||||
|
...rest
|
||||||
|
} = input
|
||||||
|
const location = GoogleVertexShared.location(inputLocation, "global")
|
||||||
|
const project = GoogleVertexShared.project(inputProject)
|
||||||
|
return route.with({
|
||||||
|
...rest,
|
||||||
|
endpoint: {
|
||||||
|
baseURL:
|
||||||
|
baseURL ??
|
||||||
|
`https://aiplatform.googleapis.com/v1/projects/${GoogleVertexShared.requireProject(project)}/locations/${location}/endpoints/openapi`,
|
||||||
|
},
|
||||||
|
auth: GoogleVertexShared.oauth(input, project),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const configure = (input: Config = {}) => {
|
||||||
|
const route = configuredRoute(input)
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
model: (modelID: string | ModelID) => route.model({ id: modelID }),
|
||||||
|
configure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const provider = {
|
||||||
|
id,
|
||||||
|
configure,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const model: ProviderPackage.Definition<Settings>["model"] = (modelID, settings) => {
|
||||||
|
if (settings.apiKey !== undefined) throw new Error("Google Vertex Chat does not support API keys")
|
||||||
|
return configure({
|
||||||
|
accessToken: settings.accessToken,
|
||||||
|
baseURL: settings.baseURL,
|
||||||
|
headers: settings.headers === undefined ? undefined : { ...settings.headers },
|
||||||
|
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
|
||||||
|
limits: settings.limits,
|
||||||
|
location: settings.location,
|
||||||
|
project: settings.project,
|
||||||
|
providerOptions: settings.providerOptions,
|
||||||
|
}).model(modelID)
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { Effect, Schema, Struct } from "effect"
|
||||||
|
import type { ProviderPackage } from "../provider-package"
|
||||||
|
import { AnthropicMessages } from "../protocols/anthropic-messages"
|
||||||
|
import { Auth } from "../route/auth"
|
||||||
|
import { Route, type RouteDefaultsInput } from "../route/client"
|
||||||
|
import { Endpoint } from "../route/endpoint"
|
||||||
|
import { Framing } from "../route/framing"
|
||||||
|
import { Protocol } from "../route/protocol"
|
||||||
|
import { ProviderID, type ModelID, type ProviderOptions } from "../schema"
|
||||||
|
import { GoogleVertexShared } from "./google-vertex-shared"
|
||||||
|
|
||||||
|
const VERSION = "vertex-2023-10-16" as const
|
||||||
|
|
||||||
|
// models.dev uses this provider id even though the API contract is Anthropic Messages.
|
||||||
|
export const id = ProviderID.make("google-vertex-anthropic")
|
||||||
|
|
||||||
|
export type Config = RouteDefaultsInput &
|
||||||
|
GoogleVertexShared.OAuthOptions & {
|
||||||
|
readonly baseURL?: string
|
||||||
|
readonly location?: string
|
||||||
|
readonly project?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Settings extends ProviderPackage.Settings {
|
||||||
|
readonly accessToken?: string
|
||||||
|
readonly apiKey?: never
|
||||||
|
readonly baseURL?: string
|
||||||
|
readonly location?: string
|
||||||
|
readonly project?: string
|
||||||
|
readonly providerOptions?: ProviderOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
const route = Route.make({
|
||||||
|
id: "google-vertex-messages",
|
||||||
|
provider: id,
|
||||||
|
providerMetadataKey: "anthropic",
|
||||||
|
protocol: Protocol.make({
|
||||||
|
id: AnthropicMessages.protocol.id,
|
||||||
|
body: {
|
||||||
|
schema: Schema.Struct({
|
||||||
|
...Struct.omit(AnthropicMessages.AnthropicMessagesBody.fields, ["model"]),
|
||||||
|
anthropic_version: Schema.Literal(VERSION),
|
||||||
|
}),
|
||||||
|
from: (request) =>
|
||||||
|
AnthropicMessages.protocol.body.from(request).pipe(
|
||||||
|
Effect.map((body) => ({
|
||||||
|
...Struct.omit(body, ["model"]),
|
||||||
|
anthropic_version: VERSION,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
stream: AnthropicMessages.protocol.stream,
|
||||||
|
}),
|
||||||
|
endpoint: Endpoint.path(({ request }) => `/${request.model.id}:streamRawPredict`),
|
||||||
|
auth: Auth.none,
|
||||||
|
framing: Framing.sse,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const routes = [route]
|
||||||
|
|
||||||
|
const configuredRoute = (input: Config) => {
|
||||||
|
if ("apiKey" in input && input.apiKey !== undefined)
|
||||||
|
throw new Error("Google Vertex Messages does not support API keys")
|
||||||
|
const {
|
||||||
|
accessToken: _accessToken,
|
||||||
|
auth: _auth,
|
||||||
|
baseURL,
|
||||||
|
location: inputLocation,
|
||||||
|
project: inputProject,
|
||||||
|
...rest
|
||||||
|
} = input
|
||||||
|
const location = GoogleVertexShared.location(inputLocation, "global")
|
||||||
|
const project = GoogleVertexShared.project(inputProject)
|
||||||
|
return route.with({
|
||||||
|
...rest,
|
||||||
|
endpoint: {
|
||||||
|
baseURL:
|
||||||
|
baseURL ??
|
||||||
|
`https://${GoogleVertexShared.host(location)}/v1/projects/${GoogleVertexShared.requireProject(project)}/locations/${location}/publishers/anthropic/models`,
|
||||||
|
},
|
||||||
|
auth: GoogleVertexShared.oauth(input, project),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const configure = (input: Config = {}) => {
|
||||||
|
const route = configuredRoute(input)
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
model: (modelID: string | ModelID) => route.model({ id: modelID }),
|
||||||
|
configure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const provider = {
|
||||||
|
id,
|
||||||
|
configure,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const model: ProviderPackage.Definition<Settings>["model"] = (modelID, settings) => {
|
||||||
|
if (settings.apiKey !== undefined) throw new Error("Google Vertex Messages does not support API keys")
|
||||||
|
return configure({
|
||||||
|
accessToken: settings.accessToken,
|
||||||
|
baseURL: settings.baseURL,
|
||||||
|
headers: settings.headers === undefined ? undefined : { ...settings.headers },
|
||||||
|
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
|
||||||
|
limits: settings.limits,
|
||||||
|
location: settings.location,
|
||||||
|
project: settings.project,
|
||||||
|
providerOptions: settings.providerOptions,
|
||||||
|
}).model(modelID)
|
||||||
|
}
|
||||||
+12
-7
@@ -1,10 +1,10 @@
|
|||||||
import type { ProviderPackage } from "../provider-package"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { GoogleVertexAnthropic } from "../protocols/google-vertex-anthropic"
|
import { OpenAICompatibleResponses } from "../protocols/openai-compatible-responses"
|
||||||
import type { RouteDefaultsInput } from "../route/client"
|
import type { RouteDefaultsInput } from "../route/client"
|
||||||
import { ProviderID, type ModelID, type ProviderOptions } from "../schema"
|
import { ProviderID, type ModelID, type ProviderOptions } from "../schema"
|
||||||
import { GoogleVertexShared } from "./google-vertex-shared"
|
import { GoogleVertexShared } from "./google-vertex-shared"
|
||||||
|
|
||||||
export const id = ProviderID.make("google-vertex-anthropic")
|
export const id = ProviderID.make("google-vertex")
|
||||||
|
|
||||||
export type Config = RouteDefaultsInput &
|
export type Config = RouteDefaultsInput &
|
||||||
GoogleVertexShared.OAuthOptions & {
|
GoogleVertexShared.OAuthOptions & {
|
||||||
@@ -22,11 +22,16 @@ export interface Settings extends ProviderPackage.Settings {
|
|||||||
readonly providerOptions?: ProviderOptions
|
readonly providerOptions?: ProviderOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export const routes = [GoogleVertexAnthropic.route]
|
const route = OpenAICompatibleResponses.route.with({
|
||||||
|
id: "google-vertex-responses",
|
||||||
|
provider: id,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const routes = [route]
|
||||||
|
|
||||||
const configuredRoute = (input: Config) => {
|
const configuredRoute = (input: Config) => {
|
||||||
if ("apiKey" in input && input.apiKey !== undefined)
|
if ("apiKey" in input && input.apiKey !== undefined)
|
||||||
throw new Error("Google Vertex Anthropic does not support API keys")
|
throw new Error("Google Vertex Responses does not support API keys")
|
||||||
const {
|
const {
|
||||||
accessToken: _accessToken,
|
accessToken: _accessToken,
|
||||||
auth: _auth,
|
auth: _auth,
|
||||||
@@ -37,12 +42,12 @@ const configuredRoute = (input: Config) => {
|
|||||||
} = input
|
} = input
|
||||||
const location = GoogleVertexShared.location(inputLocation, "global")
|
const location = GoogleVertexShared.location(inputLocation, "global")
|
||||||
const project = GoogleVertexShared.project(inputProject)
|
const project = GoogleVertexShared.project(inputProject)
|
||||||
return GoogleVertexAnthropic.route.with({
|
return route.with({
|
||||||
...rest,
|
...rest,
|
||||||
endpoint: {
|
endpoint: {
|
||||||
baseURL:
|
baseURL:
|
||||||
baseURL ??
|
baseURL ??
|
||||||
`https://${GoogleVertexShared.host(location)}/v1/projects/${GoogleVertexShared.requireProject(project)}/locations/${location}/publishers/anthropic/models`,
|
`https://aiplatform.googleapis.com/v1/projects/${GoogleVertexShared.requireProject(project)}/locations/${location}/endpoints/openapi`,
|
||||||
},
|
},
|
||||||
auth: GoogleVertexShared.oauth(input, project),
|
auth: GoogleVertexShared.oauth(input, project),
|
||||||
})
|
})
|
||||||
@@ -63,7 +68,7 @@ export const provider = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const model: ProviderPackage.Definition<Settings>["model"] = (modelID, settings) => {
|
export const model: ProviderPackage.Definition<Settings>["model"] = (modelID, settings) => {
|
||||||
if (settings.apiKey !== undefined) throw new Error("Google Vertex Anthropic does not support API keys")
|
if (settings.apiKey !== undefined) throw new Error("Google Vertex Responses does not support API keys")
|
||||||
return configure({
|
return configure({
|
||||||
accessToken: settings.accessToken,
|
accessToken: settings.accessToken,
|
||||||
baseURL: settings.baseURL,
|
baseURL: settings.baseURL,
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import type { ProviderPackage } from "../provider-package"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { GoogleVertexGemini } from "../protocols/google-vertex-gemini"
|
import { Gemini } from "../protocols/gemini"
|
||||||
import { Auth } from "../route/auth"
|
import { Auth } from "../route/auth"
|
||||||
import type { RouteDefaultsInput } from "../route/client"
|
import { Route, type RouteDefaultsInput } from "../route/client"
|
||||||
|
import { Endpoint } from "../route/endpoint"
|
||||||
|
import { Framing } from "../route/framing"
|
||||||
import { ProviderID, type ModelID, type ProviderOptions } from "../schema"
|
import { ProviderID, type ModelID, type ProviderOptions } from "../schema"
|
||||||
import { GoogleVertexShared } from "./google-vertex-shared"
|
import { GoogleVertexShared } from "./google-vertex-shared"
|
||||||
|
|
||||||
@@ -25,7 +27,20 @@ export type Settings = ProviderPackage.Settings &
|
|||||||
readonly providerOptions?: ProviderOptions
|
readonly providerOptions?: ProviderOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export const routes = [GoogleVertexGemini.route]
|
const route = Route.make({
|
||||||
|
id: "google-vertex-gemini",
|
||||||
|
provider: id,
|
||||||
|
providerMetadataKey: "google",
|
||||||
|
protocol: Gemini.protocol,
|
||||||
|
endpoint: Endpoint.path(({ request }) => {
|
||||||
|
const model = String(request.model.id)
|
||||||
|
return `/${model.startsWith("endpoints/") ? model : `models/${model}`}:streamGenerateContent?alt=sse`
|
||||||
|
}),
|
||||||
|
auth: Auth.none,
|
||||||
|
framing: Framing.sse,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const routes = [route]
|
||||||
|
|
||||||
const configuredRoute = (input: Config, modelID: string | ModelID) => {
|
const configuredRoute = (input: Config, modelID: string | ModelID) => {
|
||||||
const {
|
const {
|
||||||
@@ -48,7 +63,7 @@ const configuredRoute = (input: Config, modelID: string | ModelID) => {
|
|||||||
(apiKey
|
(apiKey
|
||||||
? "https://aiplatform.googleapis.com/v1/publishers/google"
|
? "https://aiplatform.googleapis.com/v1/publishers/google"
|
||||||
: `https://${GoogleVertexShared.host(location)}/v1beta1/projects/${GoogleVertexShared.requireProject(project)}/locations/${location}${endpointModel ? "" : "/publishers/google"}`)
|
: `https://${GoogleVertexShared.host(location)}/v1beta1/projects/${GoogleVertexShared.requireProject(project)}/locations/${location}${endpointModel ? "" : "/publishers/google"}`)
|
||||||
return GoogleVertexGemini.route.with({
|
return route.with({
|
||||||
...rest,
|
...rest,
|
||||||
endpoint: { baseURL: endpoint },
|
endpoint: { baseURL: endpoint },
|
||||||
auth: apiKey === undefined ? GoogleVertexShared.oauth(input, project) : Auth.header("x-goog-api-key", apiKey),
|
auth: apiKey === undefined ? GoogleVertexShared.oauth(input, project) : Auth.header("x-goog-api-key", apiKey),
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
export { model } from "../google-vertex-anthropic"
|
|
||||||
export type { Settings } from "../google-vertex-anthropic"
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { model } from "../google-vertex-chat"
|
||||||
|
export type { Settings } from "../google-vertex-chat"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { model } from "../google-vertex"
|
||||||
|
export type { Settings } from "../google-vertex"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { model } from "../google-vertex-messages"
|
||||||
|
export type { Settings } from "../google-vertex-messages"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { model } from "../google-vertex-responses"
|
||||||
|
export type { Settings } from "../google-vertex-responses"
|
||||||
@@ -7,7 +7,9 @@ export { CloudflareAIGateway, CloudflareWorkersAI } from "./cloudflare"
|
|||||||
export * as GitHubCopilot from "./github-copilot"
|
export * as GitHubCopilot from "./github-copilot"
|
||||||
export * as Google from "./google"
|
export * as Google from "./google"
|
||||||
export * as GoogleVertex from "./google-vertex"
|
export * as GoogleVertex from "./google-vertex"
|
||||||
export * as GoogleVertexAnthropic from "./google-vertex-anthropic"
|
export * as GoogleVertexChat from "./google-vertex-chat"
|
||||||
|
export * as GoogleVertexMessages from "./google-vertex-messages"
|
||||||
|
export * as GoogleVertexResponses from "./google-vertex-responses"
|
||||||
export * as OpenAI from "./openai"
|
export * as OpenAI from "./openai"
|
||||||
export * as OpenAICompatible from "./openai-compatible"
|
export * as OpenAICompatible from "./openai-compatible"
|
||||||
export * as OpenAICompatibleResponses from "./openai-compatible-responses"
|
export * as OpenAICompatibleResponses from "./openai-compatible-responses"
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
|
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
|
||||||
import type { Route, RouteDefaultsInput } from "../route/client"
|
import type { Route, RouteDefaultsInput } from "../route/client"
|
||||||
import type { ProviderPackage } from "../provider-package"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { ProviderID, type ModelID } from "../schema"
|
import { HttpOptions, ProviderID, ToolDefinition, mergeHttpOptions, type ModelID } from "../schema"
|
||||||
import * as OpenAIChat from "../protocols/openai-chat"
|
import * as OpenAIChat from "../protocols/openai-chat"
|
||||||
import * as OpenAIResponses from "../protocols/openai-responses"
|
import * as OpenAIResponses from "../protocols/openai-responses"
|
||||||
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options"
|
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options"
|
||||||
|
import { OpenAIImages, type OpenAIImageOptions } from "../protocols/openai-images"
|
||||||
|
|
||||||
export type { OpenAIOptionsInput, OpenAIResponseIncludable } from "./openai-options"
|
export type { OpenAIOptionsInput, OpenAIResponseIncludable } from "./openai-options"
|
||||||
|
export type { OpenAIImageOptions } from "../protocols/openai-images"
|
||||||
|
|
||||||
export const id = ProviderID.make("openai")
|
export const id = ProviderID.make("openai")
|
||||||
|
|
||||||
@@ -20,8 +22,44 @@ export type Config = RouteDefaultsInput &
|
|||||||
readonly baseURL?: string
|
readonly baseURL?: string
|
||||||
readonly queryParams?: Record<string, string>
|
readonly queryParams?: Record<string, string>
|
||||||
readonly providerOptions?: OpenAIProviderOptionsInput
|
readonly providerOptions?: OpenAIProviderOptionsInput
|
||||||
|
readonly image?: ImageConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ImageConfig {
|
||||||
|
readonly providerOptions?: OpenAIImageOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImageGenerationOptions {
|
||||||
|
readonly action?: "auto" | "generate" | "edit"
|
||||||
|
readonly background?: "auto" | "opaque" | "transparent"
|
||||||
|
readonly inputFidelity?: "low" | "high"
|
||||||
|
readonly outputCompression?: number
|
||||||
|
readonly outputFormat?: "png" | "jpeg" | "webp"
|
||||||
|
readonly partialImages?: number
|
||||||
|
readonly quality?: "auto" | "low" | "medium" | "high"
|
||||||
|
readonly size?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const imageGeneration = (options: ImageGenerationOptions = {}) =>
|
||||||
|
ToolDefinition.make({
|
||||||
|
name: "image_generation",
|
||||||
|
description: "Generate or edit an image using OpenAI's hosted image generation tool.",
|
||||||
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
||||||
|
native: {
|
||||||
|
openai: {
|
||||||
|
type: "image_generation",
|
||||||
|
action: options.action,
|
||||||
|
background: options.background,
|
||||||
|
input_fidelity: options.inputFidelity,
|
||||||
|
output_compression: options.outputCompression,
|
||||||
|
output_format: options.outputFormat,
|
||||||
|
partial_images: options.partialImages,
|
||||||
|
quality: options.quality,
|
||||||
|
size: options.size,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export interface Settings extends ProviderPackage.Settings {
|
export interface Settings extends ProviderPackage.Settings {
|
||||||
readonly apiKey?: string
|
readonly apiKey?: string
|
||||||
readonly baseURL?: string
|
readonly baseURL?: string
|
||||||
@@ -35,7 +73,7 @@ export interface Settings extends ProviderPackage.Settings {
|
|||||||
const auth = (options: ProviderAuthOption<"optional">) => AuthOptions.bearer(options, "OPENAI_API_KEY")
|
const auth = (options: ProviderAuthOption<"optional">) => AuthOptions.bearer(options, "OPENAI_API_KEY")
|
||||||
|
|
||||||
const defaults = (input: Config) => {
|
const defaults = (input: Config) => {
|
||||||
const { apiKey: _, auth: _auth, baseURL: _baseURL, queryParams: _queryParams, ...rest } = input
|
const { apiKey: _, auth: _auth, baseURL: _baseURL, queryParams: _queryParams, image: _image, ...rest } = input
|
||||||
return rest
|
return rest
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +93,21 @@ export const configure = (input: Config = {}) => {
|
|||||||
const responsesWebSocket = (id: string | ModelID) =>
|
const responsesWebSocket = (id: string | ModelID) =>
|
||||||
responsesWebSocketRoute.with(withOpenAIOptions(id, modelDefaults, { textVerbosity: true })).model({ id })
|
responsesWebSocketRoute.with(withOpenAIOptions(id, modelDefaults, { textVerbosity: true })).model({ id })
|
||||||
const chat = (id: string | ModelID) => chatRoute.with(withOpenAIOptions(id, modelDefaults)).model({ id })
|
const chat = (id: string | ModelID) => chatRoute.with(withOpenAIOptions(id, modelDefaults)).model({ id })
|
||||||
|
const image = (modelID: string | ModelID) =>
|
||||||
|
OpenAIImages.model({
|
||||||
|
id: modelID,
|
||||||
|
auth: auth(input),
|
||||||
|
baseURL: input.baseURL,
|
||||||
|
headers: input.headers,
|
||||||
|
defaults: {
|
||||||
|
providerOptions:
|
||||||
|
input.image?.providerOptions === undefined ? undefined : { openai: { ...input.image.providerOptions } },
|
||||||
|
http: mergeHttpOptions(
|
||||||
|
input.http === undefined ? undefined : HttpOptions.make(input.http),
|
||||||
|
input.queryParams === undefined ? undefined : new HttpOptions({ query: input.queryParams }),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
@@ -62,6 +115,7 @@ export const configure = (input: Config = {}) => {
|
|||||||
responses,
|
responses,
|
||||||
responsesWebSocket,
|
responsesWebSocket,
|
||||||
chat,
|
chat,
|
||||||
|
image,
|
||||||
configure,
|
configure,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,3 +151,4 @@ export const chatModel: ProviderPackage.Definition<Settings>["model"] = (modelID
|
|||||||
export const responses = provider.responses
|
export const responses = provider.responses
|
||||||
export const responsesWebSocket = provider.responsesWebSocket
|
export const responsesWebSocket = provider.responsesWebSocket
|
||||||
export const chat = provider.chat
|
export const chat = provider.chat
|
||||||
|
export const image = provider.image
|
||||||
|
|||||||
@@ -41,13 +41,31 @@ export const protocol = Protocol.make({
|
|||||||
schema: OpenRouterBody,
|
schema: OpenRouterBody,
|
||||||
from: (request) =>
|
from: (request) =>
|
||||||
OpenAIChat.protocol.body.from(request).pipe(
|
OpenAIChat.protocol.body.from(request).pipe(
|
||||||
Effect.map(
|
Effect.map((body) => {
|
||||||
(body) =>
|
const sourceAssistants = request.messages.filter((message) => message.role === "assistant")
|
||||||
({
|
let assistantIndex = 0
|
||||||
...body,
|
const messages = body.messages.map((message) => {
|
||||||
...bodyOptions(request.providerOptions?.openrouter),
|
if (message.role !== "assistant") return message
|
||||||
}) as OpenRouterBody,
|
const source = sourceAssistants[assistantIndex++]
|
||||||
),
|
const reasoning = source?.content
|
||||||
|
.filter((part) => part.type === "reasoning")
|
||||||
|
.map((part) => part.text)
|
||||||
|
.join("")
|
||||||
|
const reasoningDetails = Array.isArray(message.reasoning_details) ? message.reasoning_details : undefined
|
||||||
|
return {
|
||||||
|
...message,
|
||||||
|
reasoning_content: undefined,
|
||||||
|
reasoning_text: undefined,
|
||||||
|
reasoning: reasoning && reasoningDetails && reasoningDetails.length > 0 ? reasoning : undefined,
|
||||||
|
reasoning_details: reasoningDetails,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
...body,
|
||||||
|
messages,
|
||||||
|
...bodyOptions(request.providerOptions?.openrouter),
|
||||||
|
} as OpenRouterBody
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
stream: OpenAIChat.protocol.stream,
|
stream: OpenAIChat.protocol.stream,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Config, Effect, Redacted } from "effect"
|
import { Config, Effect, Redacted } from "effect"
|
||||||
import { Headers } from "effect/unstable/http"
|
import { Headers } from "effect/unstable/http"
|
||||||
import { AuthenticationReason, InvalidRequestReason, LLMError, type LLMRequest } from "../schema"
|
import { AuthenticationReason, InvalidRequestReason, LLMError, type HttpOptions } from "../schema"
|
||||||
|
|
||||||
export class MissingCredentialError extends Error {
|
export class MissingCredentialError extends Error {
|
||||||
readonly _tag = "MissingCredentialError"
|
readonly _tag = "MissingCredentialError"
|
||||||
@@ -15,7 +15,7 @@ export type AuthError = CredentialError | LLMError
|
|||||||
type Secret = string | Redacted.Redacted | Config.Config<string | Redacted.Redacted>
|
type Secret = string | Redacted.Redacted | Config.Config<string | Redacted.Redacted>
|
||||||
|
|
||||||
export interface AuthInput {
|
export interface AuthInput {
|
||||||
readonly request: LLMRequest
|
readonly request: { readonly http?: HttpOptions }
|
||||||
readonly method: "POST" | "GET"
|
readonly method: "POST" | "GET"
|
||||||
readonly url: string
|
readonly url: string
|
||||||
readonly body: string
|
readonly body: string
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ export const ToolInputStart = Schema.Struct({
|
|||||||
type: Schema.tag("tool-input-start"),
|
type: Schema.tag("tool-input-start"),
|
||||||
id: ToolCallID,
|
id: ToolCallID,
|
||||||
name: Schema.String,
|
name: Schema.String,
|
||||||
|
providerExecuted: Schema.optional(Schema.Boolean),
|
||||||
providerMetadata: Schema.optional(ProviderMetadata),
|
providerMetadata: Schema.optional(ProviderMetadata),
|
||||||
}).annotate({ identifier: "LLM.Event.ToolInputStart" })
|
}).annotate({ identifier: "LLM.Event.ToolInputStart" })
|
||||||
export type ToolInputStart = Schema.Schema.Type<typeof ToolInputStart>
|
export type ToolInputStart = Schema.Schema.Type<typeof ToolInputStart>
|
||||||
@@ -149,6 +150,15 @@ export const ToolInputEnd = Schema.Struct({
|
|||||||
}).annotate({ identifier: "LLM.Event.ToolInputEnd" })
|
}).annotate({ identifier: "LLM.Event.ToolInputEnd" })
|
||||||
export type ToolInputEnd = Schema.Schema.Type<typeof ToolInputEnd>
|
export type ToolInputEnd = Schema.Schema.Type<typeof ToolInputEnd>
|
||||||
|
|
||||||
|
/** A local tool call whose final input could not be decoded. */
|
||||||
|
export const ToolInputError = Schema.Struct({
|
||||||
|
type: Schema.tag("tool-input-error"),
|
||||||
|
id: ToolCallID,
|
||||||
|
name: Schema.String,
|
||||||
|
raw: Schema.String,
|
||||||
|
}).annotate({ identifier: "LLM.Event.ToolInputError" })
|
||||||
|
export type ToolInputError = Schema.Schema.Type<typeof ToolInputError>
|
||||||
|
|
||||||
export const ToolCall = Schema.Struct({
|
export const ToolCall = Schema.Struct({
|
||||||
type: Schema.tag("tool-call"),
|
type: Schema.tag("tool-call"),
|
||||||
id: ToolCallID,
|
id: ToolCallID,
|
||||||
@@ -216,6 +226,7 @@ const llmEventTagged = Schema.Union([
|
|||||||
ToolInputStart,
|
ToolInputStart,
|
||||||
ToolInputDelta,
|
ToolInputDelta,
|
||||||
ToolInputEnd,
|
ToolInputEnd,
|
||||||
|
ToolInputError,
|
||||||
ToolCall,
|
ToolCall,
|
||||||
ToolResult,
|
ToolResult,
|
||||||
ToolError,
|
ToolError,
|
||||||
@@ -253,6 +264,8 @@ export const LLMEvent = Object.assign(llmEventTagged, {
|
|||||||
toolInputDelta: (input: WithID<ToolInputDelta, ToolCallID>) =>
|
toolInputDelta: (input: WithID<ToolInputDelta, ToolCallID>) =>
|
||||||
ToolInputDelta.make({ ...input, id: toolCallID(input.id) }),
|
ToolInputDelta.make({ ...input, id: toolCallID(input.id) }),
|
||||||
toolInputEnd: (input: WithID<ToolInputEnd, ToolCallID>) => ToolInputEnd.make({ ...input, id: toolCallID(input.id) }),
|
toolInputEnd: (input: WithID<ToolInputEnd, ToolCallID>) => ToolInputEnd.make({ ...input, id: toolCallID(input.id) }),
|
||||||
|
toolInputError: (input: WithID<ToolInputError, ToolCallID>) =>
|
||||||
|
ToolInputError.make({ ...input, id: toolCallID(input.id) }),
|
||||||
toolCall: (input: WithID<ToolCall, ToolCallID>) => ToolCall.make({ ...input, id: toolCallID(input.id) }),
|
toolCall: (input: WithID<ToolCall, ToolCallID>) => ToolCall.make({ ...input, id: toolCallID(input.id) }),
|
||||||
toolResult: (input: WithID<ToolResult, ToolCallID>) =>
|
toolResult: (input: WithID<ToolResult, ToolCallID>) =>
|
||||||
ToolResult.make({
|
ToolResult.make({
|
||||||
@@ -283,6 +296,7 @@ export const LLMEvent = Object.assign(llmEventTagged, {
|
|||||||
toolInputStart: llmEventTagged.guards["tool-input-start"],
|
toolInputStart: llmEventTagged.guards["tool-input-start"],
|
||||||
toolInputDelta: llmEventTagged.guards["tool-input-delta"],
|
toolInputDelta: llmEventTagged.guards["tool-input-delta"],
|
||||||
toolInputEnd: llmEventTagged.guards["tool-input-end"],
|
toolInputEnd: llmEventTagged.guards["tool-input-end"],
|
||||||
|
toolInputError: llmEventTagged.guards["tool-input-error"],
|
||||||
toolCall: llmEventTagged.guards["tool-call"],
|
toolCall: llmEventTagged.guards["tool-call"],
|
||||||
toolResult: llmEventTagged.guards["tool-result"],
|
toolResult: llmEventTagged.guards["tool-result"],
|
||||||
toolError: llmEventTagged.guards["tool-error"],
|
toolError: llmEventTagged.guards["tool-error"],
|
||||||
@@ -548,6 +562,10 @@ const reduceResponseState = (state: ResponseState, event: LLMEvent): ResponseSta
|
|||||||
return reduceToolInputDelta(next, event)
|
return reduceToolInputDelta(next, event)
|
||||||
case "tool-input-end":
|
case "tool-input-end":
|
||||||
return reduceToolInputEnd(next, event)
|
return reduceToolInputEnd(next, event)
|
||||||
|
case "tool-input-error": {
|
||||||
|
const { [event.id]: _finished, ...toolInputs } = next.toolInputs
|
||||||
|
return { ...next, toolInputs }
|
||||||
|
}
|
||||||
case "tool-call":
|
case "tool-call":
|
||||||
return reduceToolCall(next, event)
|
return reduceToolCall(next, event)
|
||||||
case "tool-result":
|
case "tool-result":
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import * as Cloudflare from "../src/providers/cloudflare"
|
|||||||
import * as GitHubCopilot from "../src/providers/github-copilot"
|
import * as GitHubCopilot from "../src/providers/github-copilot"
|
||||||
import * as Google from "../src/providers/google"
|
import * as Google from "../src/providers/google"
|
||||||
import * as GoogleVertex from "../src/providers/google-vertex"
|
import * as GoogleVertex from "../src/providers/google-vertex"
|
||||||
import * as GoogleVertexAnthropic from "../src/providers/google-vertex-anthropic"
|
import * as GoogleVertexChat from "../src/providers/google-vertex-chat"
|
||||||
|
import * as GoogleVertexMessages from "../src/providers/google-vertex-messages"
|
||||||
|
import * as GoogleVertexResponses from "../src/providers/google-vertex-responses"
|
||||||
import * as OpenAI from "../src/providers/openai"
|
import * as OpenAI from "../src/providers/openai"
|
||||||
import * as OpenAICompatible from "../src/providers/openai-compatible"
|
import * as OpenAICompatible from "../src/providers/openai-compatible"
|
||||||
import * as OpenRouter from "../src/providers/openrouter"
|
import * as OpenRouter from "../src/providers/openrouter"
|
||||||
@@ -171,20 +173,56 @@ GoogleVertex.configure({ accessToken: "vertex-token", apiKey: "vertex-key", proj
|
|||||||
// @ts-expect-error Vertex Gemini package settings accept only one auth source.
|
// @ts-expect-error Vertex Gemini package settings accept only one auth source.
|
||||||
GoogleVertex.model("gemini-3.5-flash", { accessToken: "vertex-token", apiKey: "vertex-key", project: "project" })
|
GoogleVertex.model("gemini-3.5-flash", { accessToken: "vertex-token", apiKey: "vertex-key", project: "project" })
|
||||||
|
|
||||||
GoogleVertexAnthropic.configure({ accessToken: "vertex-token", project: "project" }).model("claude-sonnet-4-6")
|
GoogleVertexChat.configure({ accessToken: "vertex-token", project: "project" }).model("deepseek-ai/deepseek-v3.2-maas")
|
||||||
// @ts-expect-error Vertex Anthropic package settings do not accept API keys.
|
GoogleVertexChat.configure({ auth: RuntimeAuth.bearer("vertex-token"), project: "project" }).model(
|
||||||
GoogleVertexAnthropic.model("claude-sonnet-4-6", { apiKey: "vertex-key", project: "project" })
|
"deepseek-ai/deepseek-v3.2-maas",
|
||||||
GoogleVertexAnthropic.configure({ auth: RuntimeAuth.bearer("vertex-token"), project: "project" }).model(
|
|
||||||
"claude-sonnet-4-6",
|
|
||||||
)
|
)
|
||||||
GoogleVertexAnthropic.configure({ accessToken: "vertex-token", project: "project" }).model(
|
// @ts-expect-error Vertex Chat package settings do not accept API keys.
|
||||||
"claude-sonnet-4-6",
|
GoogleVertexChat.model("deepseek-ai/deepseek-v3.2-maas", { apiKey: "vertex-key", project: "project" })
|
||||||
// @ts-expect-error Vertex Anthropic model selectors only accept model ids.
|
GoogleVertexChat.configure({ accessToken: "vertex-token", project: "project" }).model(
|
||||||
|
"deepseek-ai/deepseek-v3.2-maas",
|
||||||
|
// @ts-expect-error Vertex Chat model selectors only accept model ids.
|
||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
GoogleVertexAnthropic.configure({
|
GoogleVertexChat.configure({
|
||||||
accessToken: "vertex-token",
|
accessToken: "vertex-token",
|
||||||
// @ts-expect-error Vertex Anthropic config accepts only one auth source.
|
// @ts-expect-error Vertex Chat config accepts only one auth source.
|
||||||
|
auth: RuntimeAuth.bearer("vertex-token"),
|
||||||
|
project: "project",
|
||||||
|
})
|
||||||
|
|
||||||
|
GoogleVertexResponses.configure({ accessToken: "vertex-token", project: "project" }).model("xai/grok-4.20-reasoning")
|
||||||
|
GoogleVertexResponses.configure({ auth: RuntimeAuth.bearer("vertex-token"), project: "project" }).model(
|
||||||
|
"xai/grok-4.20-reasoning",
|
||||||
|
)
|
||||||
|
// @ts-expect-error Vertex Responses package settings do not accept API keys.
|
||||||
|
GoogleVertexResponses.model("xai/grok-4.20-reasoning", { apiKey: "vertex-key", project: "project" })
|
||||||
|
GoogleVertexResponses.configure({ accessToken: "vertex-token", project: "project" }).model(
|
||||||
|
"xai/grok-4.20-reasoning",
|
||||||
|
// @ts-expect-error Vertex Responses model selectors only accept model ids.
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
GoogleVertexResponses.configure({
|
||||||
|
accessToken: "vertex-token",
|
||||||
|
// @ts-expect-error Vertex Responses config accepts only one auth source.
|
||||||
|
auth: RuntimeAuth.bearer("vertex-token"),
|
||||||
|
project: "project",
|
||||||
|
})
|
||||||
|
|
||||||
|
GoogleVertexMessages.configure({ accessToken: "vertex-token", project: "project" }).model("claude-sonnet-4-6")
|
||||||
|
// @ts-expect-error Vertex Messages package settings do not accept API keys.
|
||||||
|
GoogleVertexMessages.model("claude-sonnet-4-6", { apiKey: "vertex-key", project: "project" })
|
||||||
|
GoogleVertexMessages.configure({ auth: RuntimeAuth.bearer("vertex-token"), project: "project" }).model(
|
||||||
|
"claude-sonnet-4-6",
|
||||||
|
)
|
||||||
|
GoogleVertexMessages.configure({ accessToken: "vertex-token", project: "project" }).model(
|
||||||
|
"claude-sonnet-4-6",
|
||||||
|
// @ts-expect-error Vertex Messages model selectors only accept model ids.
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
GoogleVertexMessages.configure({
|
||||||
|
accessToken: "vertex-token",
|
||||||
|
// @ts-expect-error Vertex Messages config accepts only one auth source.
|
||||||
auth: RuntimeAuth.bearer("vertex-token"),
|
auth: RuntimeAuth.bearer("vertex-token"),
|
||||||
project: "project",
|
project: "project",
|
||||||
})
|
})
|
||||||
|
|||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"metadata": {
|
||||||
|
"provider": "minimax",
|
||||||
|
"protocol": "anthropic-messages",
|
||||||
|
"route": "anthropic-messages",
|
||||||
|
"transport": "http",
|
||||||
|
"model": "MiniMax-M3",
|
||||||
|
"tags": [
|
||||||
|
"prefix:anthropic-compatible-messages",
|
||||||
|
"provider:minimax",
|
||||||
|
"protocol:anthropic-messages",
|
||||||
|
"text",
|
||||||
|
"golden"
|
||||||
|
],
|
||||||
|
"name": "anthropic-compatible-messages/minimax-m3-anthropic-compatible-text",
|
||||||
|
"recordedAt": "2026-07-18T03:42:22.893Z"
|
||||||
|
},
|
||||||
|
"interactions": [
|
||||||
|
{
|
||||||
|
"transport": "http",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "https://api.minimax.io/anthropic/v1/messages",
|
||||||
|
"headers": {
|
||||||
|
"anthropic-version": "2023-06-01",
|
||||||
|
"content-type": "application/json"
|
||||||
|
},
|
||||||
|
"body": "{\"model\":\"MiniMax-M3\",\"system\":[{\"type\":\"text\",\"text\":\"You are concise.\"}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Reply exactly with: Hello!\"}]}],\"stream\":true,\"max_tokens\":40,\"temperature\":0}"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 200,
|
||||||
|
"headers": {
|
||||||
|
"content-type": "text/event-stream; charset=utf-8"
|
||||||
|
},
|
||||||
|
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"1a0b363d0882af316faebcec4d4855a8\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":53,\"output_tokens\":0,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":114,\"service_tier\":\"standard\"},\"service_tier\":\"standard\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Hello\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"!\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\"},\"usage\":{\"input_tokens\":53,\"output_tokens\":2,\"cache_read_input_tokens\":114,\"service_tier\":\"standard\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"metadata": {
|
||||||
|
"provider": "minimax",
|
||||||
|
"protocol": "anthropic-messages",
|
||||||
|
"route": "anthropic-messages",
|
||||||
|
"transport": "http",
|
||||||
|
"model": "MiniMax-M3",
|
||||||
|
"tags": [
|
||||||
|
"prefix:anthropic-compatible-messages",
|
||||||
|
"provider:minimax",
|
||||||
|
"protocol:anthropic-messages",
|
||||||
|
"tool",
|
||||||
|
"tool-call",
|
||||||
|
"golden"
|
||||||
|
],
|
||||||
|
"name": "anthropic-compatible-messages/minimax-m3-anthropic-compatible-tool-call",
|
||||||
|
"recordedAt": "2026-07-18T03:42:23.876Z"
|
||||||
|
},
|
||||||
|
"interactions": [
|
||||||
|
{
|
||||||
|
"transport": "http",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "https://api.minimax.io/anthropic/v1/messages",
|
||||||
|
"headers": {
|
||||||
|
"anthropic-version": "2023-06-01",
|
||||||
|
"content-type": "application/json"
|
||||||
|
},
|
||||||
|
"body": "{\"model\":\"MiniMax-M3\",\"system\":[{\"type\":\"text\",\"text\":\"Call tools exactly as requested.\"}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Call get_weather with city exactly Paris.\"}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false}}],\"tool_choice\":{\"type\":\"tool\",\"name\":\"get_weather\"},\"stream\":true,\"max_tokens\":80,\"temperature\":0}"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 200,
|
||||||
|
"headers": {
|
||||||
|
"content-type": "text/event-stream; charset=utf-8"
|
||||||
|
},
|
||||||
|
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"6731ecc323233459d1792df9a733dd98\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":0,\"output_tokens\":0,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":404,\"service_tier\":\"standard\"},\"service_tier\":\"standard\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"call_function_vkxtif4epmvm_1\",\"name\":\"get_weather\",\"input\":{}}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"city\\\": \\\"Paris\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\"},\"usage\":{\"input_tokens\":290,\"output_tokens\":27,\"cache_read_input_tokens\":114,\"service_tier\":\"standard\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"metadata": {
|
||||||
|
"provider": "minimax",
|
||||||
|
"protocol": "anthropic-messages",
|
||||||
|
"route": "anthropic-messages",
|
||||||
|
"transport": "http",
|
||||||
|
"model": "MiniMax-M3",
|
||||||
|
"tags": [
|
||||||
|
"prefix:anthropic-compatible-messages",
|
||||||
|
"provider:minimax",
|
||||||
|
"protocol:anthropic-messages",
|
||||||
|
"tool",
|
||||||
|
"tool-loop",
|
||||||
|
"golden"
|
||||||
|
],
|
||||||
|
"name": "anthropic-compatible-messages/minimax-m3-anthropic-compatible-tool-loop",
|
||||||
|
"recordedAt": "2026-07-18T03:42:25.248Z"
|
||||||
|
},
|
||||||
|
"interactions": [
|
||||||
|
{
|
||||||
|
"transport": "http",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "https://api.minimax.io/anthropic/v1/messages",
|
||||||
|
"headers": {
|
||||||
|
"anthropic-version": "2023-06-01",
|
||||||
|
"content-type": "application/json"
|
||||||
|
},
|
||||||
|
"body": "{\"model\":\"MiniMax-M3\",\"system\":[{\"type\":\"text\",\"text\":\"Use the get_weather tool exactly once. After the tool result, reply exactly: Paris is sunny.\"}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is the weather in Paris?\"}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false}}],\"stream\":true,\"max_tokens\":80,\"temperature\":0}"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 200,
|
||||||
|
"headers": {
|
||||||
|
"content-type": "text/event-stream; charset=utf-8"
|
||||||
|
},
|
||||||
|
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"3807fa12f9ecb9357df511e099da6da0\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":0,\"output_tokens\":0,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":417,\"service_tier\":\"standard\"},\"service_tier\":\"standard\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"call_function_yr64rwmre4gr_1\",\"name\":\"get_weather\",\"input\":{}}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"city\\\": \\\"Paris\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\"},\"usage\":{\"input_tokens\":303,\"output_tokens\":27,\"cache_read_input_tokens\":114,\"service_tier\":\"standard\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transport": "http",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "https://api.minimax.io/anthropic/v1/messages",
|
||||||
|
"headers": {
|
||||||
|
"anthropic-version": "2023-06-01",
|
||||||
|
"content-type": "application/json"
|
||||||
|
},
|
||||||
|
"body": "{\"model\":\"MiniMax-M3\",\"system\":[{\"type\":\"text\",\"text\":\"Use the get_weather tool exactly once. After the tool result, reply exactly: Paris is sunny.\"}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is the weather in Paris?\"}]},{\"role\":\"assistant\",\"content\":[{\"type\":\"tool_use\",\"id\":\"call_function_yr64rwmre4gr_1\",\"name\":\"get_weather\",\"input\":{\"city\":\"Paris\"}}]},{\"role\":\"user\",\"content\":[{\"type\":\"tool_result\",\"tool_use_id\":\"call_function_yr64rwmre4gr_1\",\"content\":\"{\\\"temperature\\\":22,\\\"condition\\\":\\\"sunny\\\"}\"}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get current weather for a city.\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false}}],\"stream\":true,\"max_tokens\":80,\"temperature\":0}"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 200,
|
||||||
|
"headers": {
|
||||||
|
"content-type": "text/event-stream; charset=utf-8"
|
||||||
|
},
|
||||||
|
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"92f8a1e86f29946eb2699d40a088fc08\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":41,\"output_tokens\":0,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":430,\"service_tier\":\"standard\"},\"service_tier\":\"standard\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Paris\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" is sunny.\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\"},\"usage\":{\"input_tokens\":41,\"output_tokens\":4,\"cache_read_input_tokens\":430,\"service_tier\":\"standard\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
+50
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"metadata": {
|
||||||
|
"model": "anthropic/claude-sonnet-4.6",
|
||||||
|
"tags": [
|
||||||
|
"prefix:openai-compatible-chat",
|
||||||
|
"provider:openrouter",
|
||||||
|
"protocol:openai-chat",
|
||||||
|
"reasoning"
|
||||||
|
],
|
||||||
|
"name": "openrouter-reasoning",
|
||||||
|
"recordedAt": "2026-07-18T11:28:39.267Z"
|
||||||
|
},
|
||||||
|
"interactions": [
|
||||||
|
{
|
||||||
|
"transport": "http",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "https://openrouter.ai/api/v1/chat/completions",
|
||||||
|
"headers": {
|
||||||
|
"content-type": "application/json"
|
||||||
|
},
|
||||||
|
"body": "{\"model\":\"anthropic/claude-sonnet-4.6\",\"messages\":[{\"role\":\"system\",\"content\":\"Think through the arithmetic, then reply with only the final integer.\"},{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219?\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_tokens\":1536,\"temperature\":0,\"reasoning\":{\"max_tokens\":1024}}"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 200,
|
||||||
|
"headers": {
|
||||||
|
"content-type": "text/event-stream"
|
||||||
|
},
|
||||||
|
"body": ": OPENROUTER PROCESSING\n\n: OPENROUTER PROCESSING\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"173\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"173\",\"format\":\"anthropic-claude-v1\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\n: OPENROUTER PROCESSING\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" × 219\\n\\n173 × 200 = 34,600\\n173 × 19 = 173 × 20 - 173 = 3,460 - 173 = 3,287\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" × 219\\n\\n173 × 200 = 34,600\\n173 × 19 = 173 × 20 - 173 = 3,460 - 173 = 3,287\",\"format\":\"anthropic-claude-v1\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"\\n\\n34,600 + 3,287 = 37,887\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"\\n\\n34,600 + 3,287 = 37,887\",\"format\":\"anthropic-claude-v1\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"signature\":\"EtgCCosBCA8YAipA0W4viH3kgBs43Cl5ewwVBPXTQElvzfbA2TLF4iSbKy9ZZDCSDjjAlF3Bs4ELEnP3vrrTuTioC6OB380lXQdyIDIRY2xhdWRlLXNvbm5ldC00LTY4AEIIdGhpbmtpbmdaJDRjMGYwNDZmLTI1ZmQtNDVmYi1iZmIzLWEwOGE4ZTI0OWNhNxIMMiUlJC3x/5p5PuTwGgwlc8eipZyoM94BHwMiMO45uQx/ymeOjbugi7RDVPZ4jZXSIiEbVi2CD7zPjAK5fFQoVGP1HD55v9CER823JCp6Dg5Xb7Lrk6NUd1XN2KTKrttK7mATE+IBrDTFmor/1cNeg+9gjIbxM/jn/6L5HPmh3/esEVu24Q0IGLZVoE7cTgGgxsrceKMD71Jp2XQgIWD8ltsPfWw3gSc4p+z18UuPN6LuR0mHHENTnClHrAPnOrxbDIl4ZwZgMX8YAQ==\",\"format\":\"anthropic-claude-v1\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"37887\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":null},\"finish_reason\":\"stop\",\"native_finish_reason\":\"end_turn\"}]}\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"service_tier\":\"default\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"stop\",\"native_finish_reason\":\"end_turn\"}],\"usage\":{\"prompt_tokens\":61,\"completion_tokens\":80,\"total_tokens\":141,\"cost\":0.001383,\"is_byok\":false,\"prompt_tokens_details\":{\"cached_tokens\":0,\"cache_write_tokens\":0,\"audio_tokens\":0,\"video_tokens\":0},\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"cost_details\":{\"upstream_inference_cost\":0.001383,\"upstream_inference_prompt_cost\":0.000183,\"upstream_inference_completions_cost\":0.0012},\"completion_tokens_details\":{\"reasoning_tokens\":29,\"image_tokens\":0,\"audio_tokens\":0}}}\n\ndata: [DONE]\n\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+55
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,95 @@
|
|||||||
|
import { describe, expect } from "bun:test"
|
||||||
|
import { Effect, Layer } from "effect"
|
||||||
|
import { HttpClientRequest } from "effect/unstable/http"
|
||||||
|
import { Image, ImageClient } from "../src"
|
||||||
|
import { OpenAI } from "../src/providers"
|
||||||
|
import { it } from "./lib/effect"
|
||||||
|
import { dynamicResponse } from "./lib/http"
|
||||||
|
|
||||||
|
describe("Image", () => {
|
||||||
|
it.effect("generates images through the OpenAI Images API", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const response = yield* Image.generate({
|
||||||
|
model: OpenAI.configure({
|
||||||
|
apiKey: "test",
|
||||||
|
baseURL: "https://api.openai.test/v1",
|
||||||
|
queryParams: { "api-version": "v1" },
|
||||||
|
http: { body: { deployment: "test" }, headers: { "x-default": "yes" } },
|
||||||
|
}).image("gpt-image-2"),
|
||||||
|
prompt: "A robot tending a rooftop garden",
|
||||||
|
count: 2,
|
||||||
|
size: { width: 1024, height: 1024 },
|
||||||
|
providerOptions: {
|
||||||
|
openai: { quality: "high", outputFormat: "webp" },
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
body: { request_metadata: "value" },
|
||||||
|
headers: { "x-request": "yes" },
|
||||||
|
query: { trace: "1" },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.images).toHaveLength(2)
|
||||||
|
expect(response.image?.mediaType).toBe("image/webp")
|
||||||
|
expect(response.image?.data).toEqual(Uint8Array.from([1, 2, 3]))
|
||||||
|
expect(response.image?.providerMetadata).toEqual({ openai: { revisedPrompt: "A precise robot" } })
|
||||||
|
expect(response.usage?.totalTokens).toBe(12)
|
||||||
|
}).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
ImageClient.layer.pipe(
|
||||||
|
Layer.provide(
|
||||||
|
dynamicResponse((input) =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const request = yield* HttpClientRequest.toWeb(input.request).pipe(Effect.orDie)
|
||||||
|
expect(request.url).toBe("https://api.openai.test/v1/images/generations?api-version=v1&trace=1")
|
||||||
|
expect(request.headers.get("authorization")).toBe("Bearer test")
|
||||||
|
expect(request.headers.get("x-default")).toBe("yes")
|
||||||
|
expect(request.headers.get("x-request")).toBe("yes")
|
||||||
|
expect(JSON.parse(input.text)).toEqual({
|
||||||
|
model: "gpt-image-2",
|
||||||
|
prompt: "A robot tending a rooftop garden",
|
||||||
|
n: 2,
|
||||||
|
size: "1024x1024",
|
||||||
|
quality: "high",
|
||||||
|
output_format: "webp",
|
||||||
|
deployment: "test",
|
||||||
|
request_metadata: "value",
|
||||||
|
})
|
||||||
|
return input.respond(
|
||||||
|
JSON.stringify({
|
||||||
|
data: [{ b64_json: "AQID", revised_prompt: "A precise robot" }, { b64_json: "BAUG" }],
|
||||||
|
output_format: "webp",
|
||||||
|
usage: { input_tokens: 4, output_tokens: 8, total_tokens: 12 },
|
||||||
|
}),
|
||||||
|
{ headers: { "content-type": "application/json" } },
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("rejects invalid common and OpenAI image options locally", () =>
|
||||||
|
Image.generate({
|
||||||
|
model: OpenAI.configure({ apiKey: "test", baseURL: "https://api.openai.test/v1" }).image("gpt-image-2"),
|
||||||
|
prompt: "A robot tending a rooftop garden",
|
||||||
|
count: -1,
|
||||||
|
size: { width: -1, height: 0.5 },
|
||||||
|
providerOptions: { openai: { outputCompression: 101 } },
|
||||||
|
}).pipe(
|
||||||
|
Effect.flip,
|
||||||
|
Effect.tap((error) =>
|
||||||
|
Effect.sync(() => {
|
||||||
|
expect(error.reason._tag).toBe("InvalidRequest")
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Effect.provide(
|
||||||
|
ImageClient.layer.pipe(
|
||||||
|
Layer.provide(dynamicResponse(() => Effect.die("invalid request should not reach the provider"))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -3,8 +3,30 @@ import { isContextOverflow } from "../src"
|
|||||||
import { classifyProviderFailure } from "../src/provider-error"
|
import { classifyProviderFailure } from "../src/provider-error"
|
||||||
|
|
||||||
describe("provider error classification", () => {
|
describe("provider error classification", () => {
|
||||||
test("classifies Z.AI GLM token limit messages as context overflow", () => {
|
test("classifies provider token limit messages as context overflow", () => {
|
||||||
expect(isContextOverflow("tokens in request more than max tokens allowed")).toBe(true)
|
const messages = [
|
||||||
|
"tokens in request more than max tokens allowed",
|
||||||
|
'{"error":{"type":"request_too_large","message":"Request exceeds the maximum size"}}',
|
||||||
|
"Requested token count exceeds the model's maximum context length of 131072 tokens.",
|
||||||
|
"Input length (265330) exceeds model's maximum context length (262144).",
|
||||||
|
"Input length 131393 exceeds the maximum allowed input length of 131040 tokens.",
|
||||||
|
"The input (516368 tokens) is longer than the model's context length (262144 tokens).",
|
||||||
|
"Prompt has 5,958,968 tokens, but the configured context size is 256,000 tokens",
|
||||||
|
"Too many tokens",
|
||||||
|
"Token limit exceeded",
|
||||||
|
]
|
||||||
|
|
||||||
|
expect(messages.every(isContextOverflow)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("does not classify rate limits as context overflow", () => {
|
||||||
|
const messages = [
|
||||||
|
"Throttling error: Too many tokens, please wait before trying again.",
|
||||||
|
"Rate limit exceeded, please retry after 30 seconds.",
|
||||||
|
"Too many requests. Please slow down.",
|
||||||
|
]
|
||||||
|
|
||||||
|
expect(messages.some(isContextOverflow)).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("classifies V1 plain-text rate limit fallbacks", () => {
|
test("classifies V1 plain-text rate limit fallbacks", () => {
|
||||||
|
|||||||
@@ -17,12 +17,16 @@ describe("provider package entrypoints", () => {
|
|||||||
import("@opencode-ai/ai/providers/azure/chat"),
|
import("@opencode-ai/ai/providers/azure/chat"),
|
||||||
import("@opencode-ai/ai/providers/google"),
|
import("@opencode-ai/ai/providers/google"),
|
||||||
import("@opencode-ai/ai/providers/google-vertex"),
|
import("@opencode-ai/ai/providers/google-vertex"),
|
||||||
import("@opencode-ai/ai/providers/google-vertex/anthropic"),
|
import("@opencode-ai/ai/providers/google-vertex/gemini"),
|
||||||
|
import("@opencode-ai/ai/providers/google-vertex/chat"),
|
||||||
|
import("@opencode-ai/ai/providers/google-vertex/responses"),
|
||||||
|
import("@opencode-ai/ai/providers/google-vertex/messages"),
|
||||||
])
|
])
|
||||||
|
|
||||||
for (const module of modules) expect(module.model).toBeFunction()
|
for (const module of modules) expect(module.model).toBeFunction()
|
||||||
expect(modules[0].model).toBe(modules[1].model)
|
expect(modules[0].model).toBe(modules[1].model)
|
||||||
expect(modules[8].model).toBe(modules[9].model)
|
expect(modules[8].model).toBe(modules[9].model)
|
||||||
|
expect(modules[12].model).toBe(modules[13].model)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("maps package settings onto the executable model", () => {
|
test("maps package settings onto the executable model", () => {
|
||||||
@@ -179,20 +183,35 @@ describe("provider package entrypoints", () => {
|
|||||||
|
|
||||||
test("selects Vertex entrypoints with the same model contract", async () => {
|
test("selects Vertex entrypoints with the same model contract", async () => {
|
||||||
const GoogleVertex = await import("@opencode-ai/ai/providers/google-vertex")
|
const GoogleVertex = await import("@opencode-ai/ai/providers/google-vertex")
|
||||||
const GoogleVertexAnthropic = await import("@opencode-ai/ai/providers/google-vertex/anthropic")
|
const GoogleVertexGemini = await import("@opencode-ai/ai/providers/google-vertex/gemini")
|
||||||
|
const GoogleVertexChat = await import("@opencode-ai/ai/providers/google-vertex/chat")
|
||||||
|
const GoogleVertexResponses = await import("@opencode-ai/ai/providers/google-vertex/responses")
|
||||||
|
const GoogleVertexMessages = await import("@opencode-ai/ai/providers/google-vertex/messages")
|
||||||
const gemini = GoogleVertex.model("gemini-3.5-flash", {
|
const gemini = GoogleVertex.model("gemini-3.5-flash", {
|
||||||
apiKey: "fixture",
|
apiKey: "fixture",
|
||||||
headers: { "x-application": "opencode" },
|
headers: { "x-application": "opencode" },
|
||||||
body: { safetySettings: [] },
|
body: { safetySettings: [] },
|
||||||
limits: { context: 1_000_000, output: 65_536 },
|
limits: { context: 1_000_000, output: 65_536 },
|
||||||
})
|
})
|
||||||
const anthropic = GoogleVertexAnthropic.model("claude-sonnet-4-6", {
|
const messages = GoogleVertexMessages.model("claude-sonnet-4-6", {
|
||||||
|
accessToken: "fixture",
|
||||||
|
location: "global",
|
||||||
|
project: "vertex-project",
|
||||||
|
})
|
||||||
|
const chat = GoogleVertexChat.model("deepseek-ai/deepseek-v3.2-maas", {
|
||||||
|
accessToken: "fixture",
|
||||||
|
location: "global",
|
||||||
|
project: "vertex-project",
|
||||||
|
})
|
||||||
|
const responses = GoogleVertexResponses.model("xai/grok-4.20-reasoning", {
|
||||||
accessToken: "fixture",
|
accessToken: "fixture",
|
||||||
location: "global",
|
location: "global",
|
||||||
project: "vertex-project",
|
project: "vertex-project",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
expect(GoogleVertexGemini.model).toBe(GoogleVertex.model)
|
||||||
expect(gemini.route.id).toBe("google-vertex-gemini")
|
expect(gemini.route.id).toBe("google-vertex-gemini")
|
||||||
|
expect(gemini.route.protocol).toBe("gemini")
|
||||||
expect(gemini.route.endpoint.baseURL).toBe("https://aiplatform.googleapis.com/v1/publishers/google")
|
expect(gemini.route.endpoint.baseURL).toBe("https://aiplatform.googleapis.com/v1/publishers/google")
|
||||||
expect(gemini.route.defaults.headers).toEqual({ "x-application": "opencode" })
|
expect(gemini.route.defaults.headers).toEqual({ "x-application": "opencode" })
|
||||||
expect(gemini.route.defaults.http?.body).toEqual({ safetySettings: [] })
|
expect(gemini.route.defaults.http?.body).toEqual({ safetySettings: [] })
|
||||||
@@ -204,15 +223,31 @@ describe("provider package entrypoints", () => {
|
|||||||
project: "vertex-project",
|
project: "vertex-project",
|
||||||
}).route.endpoint.baseURL,
|
}).route.endpoint.baseURL,
|
||||||
).toBe("https://aiplatform.eu.rep.googleapis.com/v1beta1/projects/vertex-project/locations/eu/publishers/google")
|
).toBe("https://aiplatform.eu.rep.googleapis.com/v1beta1/projects/vertex-project/locations/eu/publishers/google")
|
||||||
expect(anthropic.route.id).toBe("google-vertex-anthropic")
|
expect(messages.route.id).toBe("google-vertex-messages")
|
||||||
expect(anthropic.route.endpoint.baseURL).toBe(
|
expect(messages.route.protocol).toBe("anthropic-messages")
|
||||||
|
expect(messages.route.endpoint.baseURL).toBe(
|
||||||
"https://aiplatform.googleapis.com/v1/projects/vertex-project/locations/global/publishers/anthropic/models",
|
"https://aiplatform.googleapis.com/v1/projects/vertex-project/locations/global/publishers/anthropic/models",
|
||||||
)
|
)
|
||||||
|
expect(chat.route.id).toBe("google-vertex-chat")
|
||||||
|
expect(chat.route.protocol).toBe("openai-chat")
|
||||||
|
expect(chat.route.endpoint).toMatchObject({
|
||||||
|
baseURL: "https://aiplatform.googleapis.com/v1/projects/vertex-project/locations/global/endpoints/openapi",
|
||||||
|
path: "/chat/completions",
|
||||||
|
})
|
||||||
|
expect(responses.route.id).toBe("google-vertex-responses")
|
||||||
|
expect(responses.route.protocol).toBe("openai-responses")
|
||||||
|
expect(responses.route.endpoint).toMatchObject({
|
||||||
|
baseURL: "https://aiplatform.googleapis.com/v1/projects/vertex-project/locations/global/endpoints/openapi",
|
||||||
|
path: "/responses",
|
||||||
|
})
|
||||||
|
expect(responses.route.defaults.providerOptions).toEqual({ openai: { store: false } })
|
||||||
})
|
})
|
||||||
|
|
||||||
test("rejects conflicting Vertex auth settings at runtime", async () => {
|
test("rejects conflicting Vertex auth settings at runtime", async () => {
|
||||||
const GoogleVertex = await import("@opencode-ai/ai/providers/google-vertex")
|
const GoogleVertex = await import("@opencode-ai/ai/providers/google-vertex")
|
||||||
const GoogleVertexAnthropic = await import("@opencode-ai/ai/providers/google-vertex/anthropic")
|
const GoogleVertexChat = await import("@opencode-ai/ai/providers/google-vertex/chat")
|
||||||
|
const GoogleVertexMessages = await import("@opencode-ai/ai/providers/google-vertex/messages")
|
||||||
|
const GoogleVertexResponses = await import("@opencode-ai/ai/providers/google-vertex/responses")
|
||||||
const Providers = await import("@opencode-ai/ai/providers")
|
const Providers = await import("@opencode-ai/ai/providers")
|
||||||
expect(() =>
|
expect(() =>
|
||||||
Reflect.apply(GoogleVertex.model, undefined, [
|
Reflect.apply(GoogleVertex.model, undefined, [
|
||||||
@@ -225,15 +260,37 @@ describe("provider package entrypoints", () => {
|
|||||||
])
|
])
|
||||||
expect(() => configured.model("gemini-3.5-flash")).toThrow("Google Vertex accessToken cannot be combined with auth")
|
expect(() => configured.model("gemini-3.5-flash")).toThrow("Google Vertex accessToken cannot be combined with auth")
|
||||||
expect(() =>
|
expect(() =>
|
||||||
Reflect.apply(GoogleVertexAnthropic.model, undefined, [
|
Reflect.apply(GoogleVertexMessages.model, undefined, [
|
||||||
"claude-sonnet-4-6",
|
"claude-sonnet-4-6",
|
||||||
{ apiKey: "fixture", project: "vertex-project" },
|
{ apiKey: "fixture", project: "vertex-project" },
|
||||||
]),
|
]),
|
||||||
).toThrow("Google Vertex Anthropic does not support API keys")
|
).toThrow("Google Vertex Messages does not support API keys")
|
||||||
expect(() =>
|
expect(() =>
|
||||||
Reflect.apply(Providers.GoogleVertexAnthropic.configure, undefined, [
|
Reflect.apply(Providers.GoogleVertexMessages.configure, undefined, [
|
||||||
{ apiKey: "fixture", project: "vertex-project" },
|
{ apiKey: "fixture", project: "vertex-project" },
|
||||||
]),
|
]),
|
||||||
).toThrow("Google Vertex Anthropic does not support API keys")
|
).toThrow("Google Vertex Messages does not support API keys")
|
||||||
|
expect(() =>
|
||||||
|
Reflect.apply(GoogleVertexChat.model, undefined, [
|
||||||
|
"deepseek-ai/deepseek-v3.2-maas",
|
||||||
|
{ apiKey: "fixture", project: "vertex-project" },
|
||||||
|
]),
|
||||||
|
).toThrow("Google Vertex Chat does not support API keys")
|
||||||
|
expect(() =>
|
||||||
|
Reflect.apply(Providers.GoogleVertexChat.configure, undefined, [
|
||||||
|
{ apiKey: "fixture", project: "vertex-project" },
|
||||||
|
]),
|
||||||
|
).toThrow("Google Vertex Chat does not support API keys")
|
||||||
|
expect(() =>
|
||||||
|
Reflect.apply(GoogleVertexResponses.model, undefined, [
|
||||||
|
"xai/grok-4.20-reasoning",
|
||||||
|
{ apiKey: "fixture", project: "vertex-project" },
|
||||||
|
]),
|
||||||
|
).toThrow("Google Vertex Responses does not support API keys")
|
||||||
|
expect(() =>
|
||||||
|
Reflect.apply(Providers.GoogleVertexResponses.configure, undefined, [
|
||||||
|
{ apiKey: "fixture", project: "vertex-project" },
|
||||||
|
]),
|
||||||
|
).toThrow("Google Vertex Responses does not support API keys")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -484,6 +484,30 @@ describe("Anthropic Messages route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("keeps malformed server tool input terminal", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const body = sseEvents(
|
||||||
|
{ type: "message_start", message: { usage: { input_tokens: 5 } } },
|
||||||
|
{
|
||||||
|
type: "content_block_start",
|
||||||
|
index: 0,
|
||||||
|
content_block: { type: "server_tool_use", id: "call_1", name: "web_search" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "content_block_delta",
|
||||||
|
index: 0,
|
||||||
|
delta: { type: "input_json_delta", partial_json: '{"query":"partial' },
|
||||||
|
},
|
||||||
|
{ type: "content_block_stop", index: 0 },
|
||||||
|
)
|
||||||
|
|
||||||
|
const error = yield* LLMClient.generate(request).pipe(Effect.provide(fixedResponse(body)), Effect.flip)
|
||||||
|
|
||||||
|
expect(error).toBeInstanceOf(LLMError)
|
||||||
|
expect(error.message).toContain("Invalid JSON input for anthropic-messages tool call web_search")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("fails with a typed provider error for stream error frames", () =>
|
it.effect("fails with a typed provider error for stream error frames", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const error = yield* LLMClient.generate(request).pipe(
|
const error = yield* LLMClient.generate(request).pipe(
|
||||||
|
|||||||
@@ -303,6 +303,32 @@ describe("Bedrock Converse route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("emits malformed tool input as an unexecuted tool error", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const body = eventStreamBody(
|
||||||
|
["messageStart", { role: "assistant" }],
|
||||||
|
[
|
||||||
|
"contentBlockStart",
|
||||||
|
{
|
||||||
|
contentBlockIndex: 0,
|
||||||
|
start: { toolUse: { toolUseId: "tool_1", name: "lookup" } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
["contentBlockDelta", { contentBlockIndex: 0, delta: { toolUse: { input: '{"query":"partial' } } }],
|
||||||
|
["contentBlockStop", { contentBlockIndex: 0 }],
|
||||||
|
["messageStop", { stopReason: "end_turn" }],
|
||||||
|
)
|
||||||
|
const response = yield* LLMClient.generate(baseRequest).pipe(Effect.provide(fixedBytes(body)))
|
||||||
|
|
||||||
|
expect(response.events.find((event) => event.type === "tool-input-error")).toMatchObject({
|
||||||
|
id: "tool_1",
|
||||||
|
name: "lookup",
|
||||||
|
raw: '{"query":"partial',
|
||||||
|
})
|
||||||
|
expect(response.finishReason).toBe("tool-calls")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("decodes reasoning deltas", () =>
|
it.effect("decodes reasoning deltas", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const body = eventStreamBody(
|
const body = eventStreamBody(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, expect } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
import { ConfigProvider, Effect, Schema } from "effect"
|
import { ConfigProvider, Effect, Schema } from "effect"
|
||||||
import { HttpClientRequest } from "effect/unstable/http"
|
import { HttpClientRequest } from "effect/unstable/http"
|
||||||
import { LLM } from "../../src"
|
import { LLM, LLMEvent } from "../../src"
|
||||||
import { CloudflareAIGateway, CloudflareWorkersAI } from "../../src/providers/cloudflare"
|
import { CloudflareAIGateway, CloudflareWorkersAI } from "../../src/providers/cloudflare"
|
||||||
import { LLMClient } from "../../src/route"
|
import { LLMClient } from "../../src/route"
|
||||||
import { it } from "../lib/effect"
|
import { it } from "../lib/effect"
|
||||||
@@ -83,6 +83,59 @@ describe("Cloudflare", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("preserves reasoning details for AI Gateway continuation", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const model = CloudflareAIGateway.configure({
|
||||||
|
accountId: "test-account",
|
||||||
|
gatewayId: "test-gateway",
|
||||||
|
apiKey: "test-token",
|
||||||
|
}).model("anthropic/claude-sonnet-4.6")
|
||||||
|
const details = [
|
||||||
|
{ type: "reasoning.text", text: "Think", format: "anthropic-claude-v1", index: 0 },
|
||||||
|
{ type: "reasoning.text", text: "ing", format: "anthropic-claude-v1", index: 0 },
|
||||||
|
{ type: "reasoning.text", signature: "signed", format: "anthropic-claude-v1", index: 0 },
|
||||||
|
]
|
||||||
|
const merged = [
|
||||||
|
{
|
||||||
|
type: "reasoning.text",
|
||||||
|
text: "Thinking",
|
||||||
|
signature: "signed",
|
||||||
|
format: "anthropic-claude-v1",
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const response = yield* LLM.generate(LLM.request({ model, prompt: "Say hello." })).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
dynamicResponse((input) =>
|
||||||
|
Effect.succeed(
|
||||||
|
input.respond(
|
||||||
|
sseEvents(
|
||||||
|
deltaChunk({ reasoning: "Think", reasoning_details: [details[0]] }),
|
||||||
|
deltaChunk({ reasoning: "ing", reasoning_details: [details[1]] }),
|
||||||
|
deltaChunk({ reasoning_details: [details[2]] }),
|
||||||
|
deltaChunk({ content: "Hello" }),
|
||||||
|
deltaChunk({}, "stop"),
|
||||||
|
),
|
||||||
|
{ headers: { "content-type": "text/event-stream" } },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.reasoning).toBe("Thinking")
|
||||||
|
expect(response.events.filter(LLMEvent.is.reasoningDelta)).toHaveLength(2)
|
||||||
|
expect(response.message.content.find((part) => part.type === "reasoning")?.providerMetadata).toEqual({
|
||||||
|
openai: { reasoningField: "reasoning", reasoningDetails: merged },
|
||||||
|
})
|
||||||
|
|
||||||
|
const replay = yield* LLMClient.prepare(LLM.request({ model, messages: [response.message] }))
|
||||||
|
expect(replay.body.messages).toEqual([
|
||||||
|
{ role: "assistant", content: "Hello", reasoning: "Thinking", reasoning_details: merged },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("defaults AI Gateway id to default when omitted or blank", () =>
|
it.effect("defaults AI Gateway id to default when omitted or blank", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as Anthropic from "../../src/providers/anthropic"
|
import * as Anthropic from "../../src/providers/anthropic"
|
||||||
|
import * as AnthropicCompatible from "../../src/providers/anthropic-compatible"
|
||||||
import { CloudflareAIGateway, CloudflareWorkersAI } from "../../src/providers/cloudflare"
|
import { CloudflareAIGateway, CloudflareWorkersAI } from "../../src/providers/cloudflare"
|
||||||
import * as Google from "../../src/providers/google"
|
import * as Google from "../../src/providers/google"
|
||||||
import * as OpenAI from "../../src/providers/openai"
|
import * as OpenAI from "../../src/providers/openai"
|
||||||
@@ -17,6 +18,11 @@ const anthropic = Anthropic.configure({
|
|||||||
})
|
})
|
||||||
const anthropicHaiku = anthropic.model("claude-haiku-4-5-20251001")
|
const anthropicHaiku = anthropic.model("claude-haiku-4-5-20251001")
|
||||||
const anthropicOpus = anthropic.model("claude-opus-4-7")
|
const anthropicOpus = anthropic.model("claude-opus-4-7")
|
||||||
|
const minimax = AnthropicCompatible.configure({
|
||||||
|
apiKey: process.env.MINIMAX_API_KEY ?? "fixture",
|
||||||
|
baseURL: "https://api.minimax.io/anthropic/v1",
|
||||||
|
provider: "minimax",
|
||||||
|
}).model("MiniMax-M3")
|
||||||
const google = Google.configure({ apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY ?? "fixture" })
|
const google = Google.configure({ apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY ?? "fixture" })
|
||||||
const gemini = google.model("gemini-2.5-flash")
|
const gemini = google.model("gemini-2.5-flash")
|
||||||
const xai = XAI.configure({ apiKey: process.env.XAI_API_KEY ?? "fixture" })
|
const xai = XAI.configure({ apiKey: process.env.XAI_API_KEY ?? "fixture" })
|
||||||
@@ -108,6 +114,15 @@ describeRecordedGoldenScenarios([
|
|||||||
{ id: "image-tool-result", temperature: false, maxTokens: 40 },
|
{ id: "image-tool-result", temperature: false, maxTokens: 40 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "MiniMax M3 Anthropic-compatible",
|
||||||
|
prefix: "anthropic-compatible-messages",
|
||||||
|
protocol: "anthropic-messages",
|
||||||
|
model: minimax,
|
||||||
|
requires: ["MINIMAX_API_KEY"],
|
||||||
|
options: { redact: { allowRequestHeaders: ["anthropic-version"] } },
|
||||||
|
scenarios: ["text", "tool-call", "tool-loop"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Gemini 2.5 Flash",
|
name: "Gemini 2.5 Flash",
|
||||||
prefix: "gemini",
|
prefix: "gemini",
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import { describe, expect } from "bun:test"
|
|||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { HttpClientRequest } from "effect/unstable/http"
|
import { HttpClientRequest } from "effect/unstable/http"
|
||||||
import { LLM } from "../../src"
|
import { LLM } from "../../src"
|
||||||
import { GoogleVertex, GoogleVertexAnthropic } from "../../src/providers"
|
import { GoogleVertex, GoogleVertexChat, GoogleVertexMessages, GoogleVertexResponses } from "../../src/providers"
|
||||||
import { LLMClient } from "../../src/route"
|
import { LLMClient } from "../../src/route"
|
||||||
import { it } from "../lib/effect"
|
import { it } from "../lib/effect"
|
||||||
import { dynamicResponse } from "../lib/http"
|
import { dynamicResponse } from "../lib/http"
|
||||||
|
import { deltaChunk, finishChunk } from "../lib/openai-chunks"
|
||||||
import { sseEvents } from "../lib/sse"
|
import { sseEvents } from "../lib/sse"
|
||||||
|
|
||||||
describe("Google Vertex providers", () => {
|
describe("Google Vertex providers", () => {
|
||||||
@@ -56,7 +57,7 @@ describe("Google Vertex providers", () => {
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const response = yield* LLMClient.generate(
|
const response = yield* LLMClient.generate(
|
||||||
LLM.request({
|
LLM.request({
|
||||||
model: GoogleVertexAnthropic.configure({
|
model: GoogleVertexMessages.configure({
|
||||||
accessToken: "vertex-token",
|
accessToken: "vertex-token",
|
||||||
location: "eu",
|
location: "eu",
|
||||||
project: "vertex-project",
|
project: "vertex-project",
|
||||||
@@ -99,11 +100,91 @@ describe("Google Vertex providers", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("protects the Vertex Anthropic API version from body overlays", () =>
|
it.effect("sends MaaS requests through Vertex Chat Completions", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const response = yield* LLMClient.generate(
|
||||||
|
LLM.request({
|
||||||
|
model: GoogleVertexChat.configure({
|
||||||
|
accessToken: "vertex-token",
|
||||||
|
location: "global",
|
||||||
|
project: "vertex-project",
|
||||||
|
}).model("deepseek-ai/deepseek-v3.2-maas"),
|
||||||
|
prompt: "Say hello.",
|
||||||
|
}),
|
||||||
|
).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
dynamicResponse((input) =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const request = yield* HttpClientRequest.toWeb(input.request).pipe(Effect.orDie)
|
||||||
|
expect(request.url).toBe(
|
||||||
|
"https://aiplatform.googleapis.com/v1/projects/vertex-project/locations/global/endpoints/openapi/chat/completions",
|
||||||
|
)
|
||||||
|
expect(request.headers.get("authorization")).toBe("Bearer vertex-token")
|
||||||
|
expect(yield* Effect.promise(() => request.json())).toMatchObject({
|
||||||
|
model: "deepseek-ai/deepseek-v3.2-maas",
|
||||||
|
messages: [{ role: "user", content: "Say hello." }],
|
||||||
|
stream: true,
|
||||||
|
stream_options: { include_usage: true },
|
||||||
|
})
|
||||||
|
return input.respond(sseEvents(deltaChunk({ content: "Hello." }), finishChunk("stop")), {
|
||||||
|
headers: { "content-type": "text/event-stream" },
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.text).toBe("Hello.")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("sends Grok requests through Vertex Responses", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const response = yield* LLMClient.generate(
|
||||||
|
LLM.request({
|
||||||
|
model: GoogleVertexResponses.configure({
|
||||||
|
accessToken: "vertex-token",
|
||||||
|
location: "global",
|
||||||
|
project: "vertex-project",
|
||||||
|
}).model("xai/grok-4.20-reasoning"),
|
||||||
|
prompt: "Say hello.",
|
||||||
|
}),
|
||||||
|
).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
dynamicResponse((input) =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const request = yield* HttpClientRequest.toWeb(input.request).pipe(Effect.orDie)
|
||||||
|
expect(request.url).toBe(
|
||||||
|
"https://aiplatform.googleapis.com/v1/projects/vertex-project/locations/global/endpoints/openapi/responses",
|
||||||
|
)
|
||||||
|
expect(request.headers.get("authorization")).toBe("Bearer vertex-token")
|
||||||
|
expect(yield* Effect.promise(() => request.json())).toMatchObject({
|
||||||
|
model: "xai/grok-4.20-reasoning",
|
||||||
|
input: [{ role: "user", content: [{ type: "input_text", text: "Say hello." }] }],
|
||||||
|
store: false,
|
||||||
|
stream: true,
|
||||||
|
})
|
||||||
|
return input.respond(
|
||||||
|
sseEvents(
|
||||||
|
{ type: "response.output_text.delta", item_id: "msg_1", delta: "Hello." },
|
||||||
|
{ type: "response.completed", response: { id: "resp_1" } },
|
||||||
|
),
|
||||||
|
{ headers: { "content-type": "text/event-stream" } },
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.text).toBe("Hello.")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("protects the Vertex Messages API version from body overlays", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const error = yield* LLMClient.prepare(
|
const error = yield* LLMClient.prepare(
|
||||||
LLM.request({
|
LLM.request({
|
||||||
model: GoogleVertexAnthropic.configure({
|
model: GoogleVertexMessages.configure({
|
||||||
accessToken: "vertex-token",
|
accessToken: "vertex-token",
|
||||||
http: { body: { anthropic_version: "wrong" } },
|
http: { body: { anthropic_version: "wrong" } },
|
||||||
project: "vertex-project",
|
project: "vertex-project",
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import { describe, expect } from "bun:test"
|
||||||
|
import { Effect } from "effect"
|
||||||
|
import { LLM, LLMEvent, LLMResponse } from "../../src"
|
||||||
|
import { OpenAIChat } from "../../src/protocols/openai-chat"
|
||||||
|
import * as OpenAICompatible from "../../src/providers/openai-compatible"
|
||||||
|
import * as OpenRouter from "../../src/providers/openrouter"
|
||||||
|
import { LLMClient } from "../../src/route"
|
||||||
|
import { recordedTests } from "../recorded-test"
|
||||||
|
import { expectWeatherToolLoop, goldenWeatherToolLoopRequest, runWeatherToolLoop } from "../recorded-scenarios"
|
||||||
|
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
name: "OpenRouter",
|
||||||
|
model: OpenRouter.configure({
|
||||||
|
apiKey: process.env.OPENROUTER_API_KEY ?? "fixture",
|
||||||
|
providerOptions: { openrouter: { reasoning: { max_tokens: 1024 } } },
|
||||||
|
}).model("anthropic/claude-sonnet-4.6"),
|
||||||
|
requires: ["OPENROUTER_API_KEY"],
|
||||||
|
cassette: "openrouter-reasoning",
|
||||||
|
structured: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Vercel AI Gateway",
|
||||||
|
model: OpenAICompatible.configure({
|
||||||
|
provider: "vercel-ai-gateway",
|
||||||
|
baseURL: "https://ai-gateway.vercel.sh/v1",
|
||||||
|
apiKey: process.env.AI_GATEWAY_API_KEY ?? "fixture",
|
||||||
|
http: { body: { reasoning: { enabled: true, max_tokens: 1024 } } },
|
||||||
|
}).model("anthropic/claude-sonnet-4.6"),
|
||||||
|
requires: ["AI_GATEWAY_API_KEY"],
|
||||||
|
cassette: "vercel-ai-gateway-reasoning",
|
||||||
|
structured: true,
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
|
for (const item of cases) {
|
||||||
|
const recorded = recordedTests({
|
||||||
|
prefix: "openai-compatible-chat",
|
||||||
|
provider: item.model.provider,
|
||||||
|
protocol: "openai-chat",
|
||||||
|
requires: item.requires,
|
||||||
|
tags: ["reasoning"],
|
||||||
|
metadata: { model: item.model.id },
|
||||||
|
})
|
||||||
|
|
||||||
|
describe(`${item.name} reasoning recorded`, () => {
|
||||||
|
recorded.effect.with(
|
||||||
|
"streams scalar reasoning",
|
||||||
|
{ cassette: item.cassette },
|
||||||
|
() =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const response = yield* LLMClient.generate(
|
||||||
|
LLM.request({
|
||||||
|
model: item.model,
|
||||||
|
system: "Think through the arithmetic, then reply with only the final integer.",
|
||||||
|
prompt: "What is 173 multiplied by 219?",
|
||||||
|
generation: { maxTokens: 1536, temperature: 0 },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.text.replaceAll(",", "").trim()).toBe("37887")
|
||||||
|
expect(response.reasoning.length).toBeGreaterThan(0)
|
||||||
|
expect(response.events.some(LLMEvent.is.reasoningDelta)).toBe(true)
|
||||||
|
const metadata = response.message.content.find((part) => part.type === "reasoning")?.providerMetadata
|
||||||
|
expect(metadata?.openai?.reasoningField).toBe(item.structured ? "reasoning" : "reasoning_content")
|
||||||
|
expect(Array.isArray(metadata?.openai?.reasoningDetails)).toBe(item.structured)
|
||||||
|
if (!item.structured) return
|
||||||
|
const details = metadata?.openai?.reasoningDetails
|
||||||
|
if (!Array.isArray(details)) return
|
||||||
|
expect(
|
||||||
|
details.some(
|
||||||
|
(detail) =>
|
||||||
|
typeof detail === "object" &&
|
||||||
|
detail !== null &&
|
||||||
|
"signature" in detail &&
|
||||||
|
typeof detail.signature === "string" &&
|
||||||
|
detail.signature.length > 0,
|
||||||
|
),
|
||||||
|
).toBe(true)
|
||||||
|
|
||||||
|
const replay = yield* LLMClient.prepare<OpenAIChat.OpenAIChatBody>(
|
||||||
|
LLM.request({ model: item.model, messages: [response.message] }),
|
||||||
|
)
|
||||||
|
expect(replay.body.messages).toMatchObject([
|
||||||
|
{ role: "assistant", content: response.text, reasoning: response.reasoning },
|
||||||
|
])
|
||||||
|
const replayDetails =
|
||||||
|
replay.body.messages[0]?.role === "assistant" ? replay.body.messages[0].reasoning_details : undefined
|
||||||
|
expect(Array.isArray(replayDetails)).toBe(true)
|
||||||
|
if (!Array.isArray(replayDetails)) return
|
||||||
|
expect(replayDetails).toEqual(details)
|
||||||
|
expect(replayDetails).toHaveLength(1)
|
||||||
|
expect(replayDetails[0]).toMatchObject({
|
||||||
|
type: "reasoning.text",
|
||||||
|
text: response.reasoning,
|
||||||
|
signature: expect.any(String),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
30_000,
|
||||||
|
)
|
||||||
|
|
||||||
|
recorded.effect.with(
|
||||||
|
"continues signed reasoning through a tool loop",
|
||||||
|
{ cassette: `${item.cassette}-tool-loop`, tags: ["continuation", "tool", "tool-loop"] },
|
||||||
|
() =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const events = yield* runWeatherToolLoop(
|
||||||
|
goldenWeatherToolLoopRequest({
|
||||||
|
id: `${item.cassette}-tool-loop`,
|
||||||
|
model: item.model,
|
||||||
|
maxTokens: 1536,
|
||||||
|
temperature: false,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expectWeatherToolLoop(events)
|
||||||
|
expect(
|
||||||
|
LLMResponse.text({
|
||||||
|
events: events.slice(events.findIndex(LLMEvent.is.stepFinish) + 1),
|
||||||
|
}).trim(),
|
||||||
|
).toMatch(/^Paris is sunny\.?$/)
|
||||||
|
const details = events
|
||||||
|
.filter(LLMEvent.is.reasoningEnd)
|
||||||
|
.map((event) => event.providerMetadata?.openai?.reasoningDetails)
|
||||||
|
.find(Array.isArray)
|
||||||
|
expect(Array.isArray(details)).toBe(item.structured)
|
||||||
|
if (!item.structured || !Array.isArray(details)) return
|
||||||
|
expect(
|
||||||
|
details.some(
|
||||||
|
(detail) =>
|
||||||
|
typeof detail === "object" &&
|
||||||
|
detail !== null &&
|
||||||
|
"signature" in detail &&
|
||||||
|
typeof detail.signature === "string" &&
|
||||||
|
detail.signature.length > 0,
|
||||||
|
),
|
||||||
|
).toBe(true)
|
||||||
|
}),
|
||||||
|
60_000,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -540,28 +540,401 @@ describe("OpenAI Chat route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("parses OpenAI-compatible reasoning content deltas", () =>
|
it.effect("parses and replays OpenAI-compatible reasoning fields", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const body = sseEvents(
|
const fields = ["reasoning_content", "reasoning", "reasoning_text"] as const
|
||||||
{ choices: [{ delta: { reasoning_content: "thinking" } }] },
|
for (const field of fields) {
|
||||||
{ choices: [{ delta: { content: "Hello" } }] },
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
{ choices: [{ delta: {}, finish_reason: "stop" }] },
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{ choices: [{ delta: { [field]: "thinking" } }] },
|
||||||
|
{ choices: [{ delta: { content: "Hello" } }] },
|
||||||
|
{ choices: [{ delta: {}, finish_reason: "stop" }] },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.reasoning).toBe("thinking")
|
||||||
|
expect(response.text).toBe("Hello")
|
||||||
|
expect(response.message.content.find((part) => part.type === "reasoning")?.providerMetadata).toEqual({
|
||||||
|
openai: { reasoningField: field },
|
||||||
|
})
|
||||||
|
|
||||||
|
const replay = yield* LLMClient.prepare<OpenAIChat.OpenAIChatBody>(
|
||||||
|
LLM.request({ model, messages: [response.message] }),
|
||||||
|
)
|
||||||
|
expect(replay.body.messages).toEqual([{ role: "assistant", content: "Hello", [field]: "thinking" }])
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("preserves and replays reasoning details alongside scalar reasoning", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const details = [
|
||||||
|
{ type: "reasoning.text", text: "thinking", format: "anthropic-claude-v1", index: 0 },
|
||||||
|
{ type: "reasoning.encrypted", data: "opaque", format: "anthropic-claude-v1", index: 1 },
|
||||||
|
]
|
||||||
|
const response = yield* LLMClient.generate(
|
||||||
|
LLM.updateRequest(request, {
|
||||||
|
tools: [{ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } }],
|
||||||
|
}),
|
||||||
|
).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{ choices: [{ delta: { reasoning: "thinking", reasoning_details: [details[0]] } }] },
|
||||||
|
{ choices: [{ delta: { reasoning_details: [details[1]] } }] },
|
||||||
|
{
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
delta: {
|
||||||
|
tool_calls: [
|
||||||
|
{ index: 0, id: "call_1", function: { name: "lookup", arguments: '{"query":"weather"}' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
finish_reason: "tool_calls",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const response = yield* LLMClient.generate(request).pipe(Effect.provide(fixedResponse(body)))
|
expect(response.reasoning).toBe("thinking")
|
||||||
|
expect(response.message.content.find((part) => part.type === "reasoning")?.providerMetadata).toEqual({
|
||||||
|
openai: { reasoningField: "reasoning", reasoningDetails: details },
|
||||||
|
})
|
||||||
|
|
||||||
|
const replay = yield* LLMClient.prepare<OpenAIChat.OpenAIChatBody>(
|
||||||
|
LLM.request({ model, messages: [response.message] }),
|
||||||
|
)
|
||||||
|
expect(replay.body.messages).toEqual([
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: null,
|
||||||
|
reasoning: "thinking",
|
||||||
|
reasoning_details: details,
|
||||||
|
tool_calls: [
|
||||||
|
{
|
||||||
|
id: "call_1",
|
||||||
|
type: "function",
|
||||||
|
function: { name: "lookup", arguments: '{"query":"weather"}' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("uses reasoning details as display fallback without inventing a scalar replay field", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const details = [
|
||||||
|
{ type: "reasoning.summary", summary: "thinking", format: "openai-responses-v1", index: 0 },
|
||||||
|
{ type: "reasoning.encrypted", data: "opaque", format: "openai-responses-v1", index: 1 },
|
||||||
|
]
|
||||||
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{ choices: [{ delta: { reasoning_details: [details[0]] } }] },
|
||||||
|
{ choices: [{ delta: { reasoning_details: [details[1]] } }] },
|
||||||
|
{ choices: [{ delta: { content: "Hello" } }] },
|
||||||
|
{ choices: [{ delta: {}, finish_reason: "stop" }] },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
expect(response.reasoning).toBe("thinking")
|
expect(response.reasoning).toBe("thinking")
|
||||||
expect(response.text).toBe("Hello")
|
expect(response.message.content.find((part) => part.type === "reasoning")?.providerMetadata).toEqual({
|
||||||
expect(response.events).toMatchObject([
|
openai: { reasoningDetails: details },
|
||||||
{ type: "step-start", index: 0 },
|
})
|
||||||
{ type: "reasoning-start", id: "reasoning-0" },
|
|
||||||
{ type: "reasoning-delta", id: "reasoning-0", text: "thinking" },
|
const replay = yield* LLMClient.prepare<OpenAIChat.OpenAIChatBody>(
|
||||||
{ type: "reasoning-end", id: "reasoning-0" },
|
LLM.request({ model, messages: [response.message] }),
|
||||||
{ type: "text-start", id: "text-0" },
|
)
|
||||||
{ type: "text-delta", id: "text-0", text: "Hello" },
|
expect(replay.body.messages).toEqual([{ role: "assistant", content: "Hello", reasoning_details: details }])
|
||||||
{ type: "text-end", id: "text-0" },
|
}),
|
||||||
{ type: "step-finish", index: 0, reason: "stop" },
|
)
|
||||||
{ type: "finish", reason: "stop" },
|
|
||||||
|
it.effect("preserves unknown reasoning details while using scalar display text", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const details = [{ type: "reasoning.future", format: "provider-v2", state: { opaque: true } }]
|
||||||
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{ choices: [{ delta: { reasoning: "thinking", reasoning_details: details } }] },
|
||||||
|
{ choices: [{ delta: { content: "Hello" } }] },
|
||||||
|
{ choices: [{ delta: {}, finish_reason: "stop" }] },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.reasoning).toBe("thinking")
|
||||||
|
expect(response.message.content.find((part) => part.type === "reasoning")?.providerMetadata).toEqual({
|
||||||
|
openai: { reasoningField: "reasoning", reasoningDetails: details },
|
||||||
|
})
|
||||||
|
|
||||||
|
const replay = yield* LLMClient.prepare<OpenAIChat.OpenAIChatBody>(
|
||||||
|
LLM.request({ model, messages: [response.message] }),
|
||||||
|
)
|
||||||
|
expect(replay.body.messages).toEqual([
|
||||||
|
{ role: "assistant", content: "Hello", reasoning: "thinking", reasoning_details: details },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("uses scalar display text for signature-only reasoning details", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const details = [{ type: "reasoning.text", signature: "signed", format: "provider-v2", index: 0 }]
|
||||||
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{ choices: [{ delta: { reasoning: "thinking", reasoning_details: details } }] },
|
||||||
|
{ choices: [{ delta: { content: "Hello" } }] },
|
||||||
|
{ choices: [{ delta: {}, finish_reason: "stop" }] },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.reasoning).toBe("thinking")
|
||||||
|
expect(response.message.content.find((part) => part.type === "reasoning")?.providerMetadata).toEqual({
|
||||||
|
openai: { reasoningField: "reasoning", reasoningDetails: details },
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("ignores scalar reasoning after content starts", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const details = [{ type: "reasoning.text", text: "detail", format: "unknown", index: 0 }]
|
||||||
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{ choices: [{ delta: { reasoning_details: details } }] },
|
||||||
|
{ choices: [{ delta: { content: "Hello" } }] },
|
||||||
|
{ choices: [{ delta: { reasoning: "scalar" } }] },
|
||||||
|
{ choices: [{ delta: {}, finish_reason: "stop" }] },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.reasoning).toBe("detail")
|
||||||
|
expect(response.events.filter(LLMEvent.is.reasoningStart)).toHaveLength(1)
|
||||||
|
expect(response.events.filter(LLMEvent.is.reasoningEnd)).toHaveLength(1)
|
||||||
|
expect(response.message.content.find((part) => part.type === "reasoning")?.providerMetadata).toEqual({
|
||||||
|
openai: { reasoningDetails: details },
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("preserves an explicitly empty reasoning details array", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{ choices: [{ delta: { reasoning_details: [] } }] },
|
||||||
|
{ choices: [{ delta: { content: "Hello" } }] },
|
||||||
|
{ choices: [{ delta: {}, finish_reason: "stop" }] },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.reasoning).toBe("")
|
||||||
|
expect(response.message.content.find((part) => part.type === "reasoning")?.providerMetadata).toEqual({
|
||||||
|
openai: { reasoningDetails: [] },
|
||||||
|
})
|
||||||
|
|
||||||
|
const replay = yield* LLMClient.prepare<OpenAIChat.OpenAIChatBody>(
|
||||||
|
LLM.request({ model, messages: [response.message] }),
|
||||||
|
)
|
||||||
|
expect(replay.body.messages).toEqual([{ role: "assistant", content: "Hello", reasoning_details: [] }])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("attaches signature-only details that arrive after content", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const details = [
|
||||||
|
{ type: "reasoning.text", text: "thinking", format: "anthropic-claude-v1", index: 0 },
|
||||||
|
{ type: "reasoning.text", signature: "signed", format: "anthropic-claude-v1", index: 0 },
|
||||||
|
]
|
||||||
|
const merged = [
|
||||||
|
{
|
||||||
|
type: "reasoning.text",
|
||||||
|
text: "thinking",
|
||||||
|
signature: "signed",
|
||||||
|
format: "anthropic-claude-v1",
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{ choices: [{ delta: { reasoning: "thinking", reasoning_details: [details[0]] } }] },
|
||||||
|
{ choices: [{ delta: { content: "Hello" } }] },
|
||||||
|
{ choices: [{ delta: { reasoning_details: [details[1]] } }] },
|
||||||
|
{ choices: [{ delta: {}, finish_reason: "stop" }] },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.reasoning).toBe("thinking")
|
||||||
|
expect(response.message.content.filter((part) => part.type === "reasoning")).toHaveLength(1)
|
||||||
|
expect(response.message.content.find((part) => part.type === "reasoning")?.providerMetadata).toEqual({
|
||||||
|
openai: { reasoningField: "reasoning", reasoningDetails: merged },
|
||||||
|
})
|
||||||
|
expect(response.events.filter(LLMEvent.is.reasoningStart)).toHaveLength(1)
|
||||||
|
expect(response.events.filter(LLMEvent.is.reasoningDelta)).toHaveLength(1)
|
||||||
|
expect(response.events.filter(LLMEvent.is.reasoningEnd)).toHaveLength(1)
|
||||||
|
expect(response.events.filter(LLMEvent.is.reasoningEnd).at(-1)?.providerMetadata).toEqual({
|
||||||
|
openai: { reasoningField: "reasoning", reasoningDetails: merged },
|
||||||
|
})
|
||||||
|
expect(response.events.findIndex(LLMEvent.is.reasoningEnd)).toBeLessThan(
|
||||||
|
response.events.findIndex(LLMEvent.is.textStart),
|
||||||
|
)
|
||||||
|
|
||||||
|
const replay = yield* LLMClient.prepare<OpenAIChat.OpenAIChatBody>(
|
||||||
|
LLM.request({ model, messages: [response.message] }),
|
||||||
|
)
|
||||||
|
expect(replay.body.messages).toEqual([
|
||||||
|
{ role: "assistant", content: "Hello", reasoning: "thinking", reasoning_details: merged },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("preserves metadata-only reasoning when the stream ends", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const details = [{ type: "reasoning.encrypted", data: "opaque", format: "openai-responses-v1", index: 0 }]
|
||||||
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{ choices: [{ delta: { reasoning_details: details } }] },
|
||||||
|
{ choices: [{ delta: {}, finish_reason: "stop" }] },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.message.content).toEqual([
|
||||||
|
{ type: "reasoning", text: "", providerMetadata: { openai: { reasoningDetails: details } } },
|
||||||
|
])
|
||||||
|
expect(response.events.filter(LLMEvent.is.reasoningStart)).toHaveLength(1)
|
||||||
|
expect(response.events.filter(LLMEvent.is.reasoningEnd)).toHaveLength(1)
|
||||||
|
|
||||||
|
const replay = yield* LLMClient.prepare<OpenAIChat.OpenAIChatBody>(
|
||||||
|
LLM.request({ model, messages: [response.message] }),
|
||||||
|
)
|
||||||
|
expect(replay.body.messages).toEqual([{ role: "assistant", content: null, reasoning_details: details }])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("flushes details-only display reasoning when the stream ends", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const details = [{ type: "reasoning.summary", summary: "summary", format: "openai-responses-v1", index: 0 }]
|
||||||
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{ choices: [{ delta: { reasoning_details: details } }] },
|
||||||
|
{ choices: [{ delta: {}, finish_reason: "stop" }] },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.reasoning).toBe("summary")
|
||||||
|
expect(response.message.content).toEqual([
|
||||||
|
{ type: "reasoning", text: "summary", providerMetadata: { openai: { reasoningDetails: details } } },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("replays details from multiple reasoning parts in order", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const first = { type: "reasoning.text", text: "first", signature: "signed-0", index: 0 }
|
||||||
|
const second = { type: "reasoning.text", text: "second", signature: "signed-1", index: 1 }
|
||||||
|
const replay = yield* LLMClient.prepare<OpenAIChat.OpenAIChatBody>(
|
||||||
|
LLM.request({
|
||||||
|
model,
|
||||||
|
messages: [
|
||||||
|
Message.assistant([
|
||||||
|
{
|
||||||
|
type: "reasoning",
|
||||||
|
text: "first",
|
||||||
|
providerMetadata: { openai: { reasoningDetails: [first] } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "reasoning",
|
||||||
|
text: "second",
|
||||||
|
providerMetadata: { openai: { reasoningField: "reasoning", reasoningDetails: [second] } },
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(replay.body.messages).toEqual([
|
||||||
|
{ role: "assistant", content: null, reasoning: "firstsecond", reasoning_details: [first, second] },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("retains scalar replay for mixed structured reasoning parts", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const detail = { type: "reasoning.encrypted", data: "opaque", index: 0 }
|
||||||
|
const replay = yield* LLMClient.prepare<OpenAIChat.OpenAIChatBody>(
|
||||||
|
LLM.request({
|
||||||
|
model,
|
||||||
|
messages: [
|
||||||
|
Message.assistant([
|
||||||
|
{
|
||||||
|
type: "reasoning",
|
||||||
|
text: "A",
|
||||||
|
providerMetadata: { openai: { reasoningDetails: [detail] } },
|
||||||
|
},
|
||||||
|
{ type: "reasoning", text: "B" },
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(replay.body.messages).toEqual([
|
||||||
|
{ role: "assistant", content: null, reasoning_content: "AB", reasoning_details: [detail] },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("replays native scalar reasoning alongside native details", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const details = [{ type: "reasoning.encrypted", data: "opaque", index: 0 }]
|
||||||
|
const replay = yield* LLMClient.prepare<OpenAIChat.OpenAIChatBody>(
|
||||||
|
LLM.request({
|
||||||
|
model,
|
||||||
|
messages: [
|
||||||
|
Message.make({
|
||||||
|
role: "assistant",
|
||||||
|
content: [{ type: "reasoning", text: "thinking" }],
|
||||||
|
native: { openaiCompatible: { reasoning_content: "thinking", reasoning_details: details } },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(replay.body.messages).toEqual([
|
||||||
|
{ role: "assistant", content: null, reasoning_content: "thinking", reasoning_details: details },
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -602,6 +975,67 @@ describe("OpenAI Chat route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("ignores empty identity fields on later tool call deltas", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const body = sseEvents(
|
||||||
|
deltaChunk({
|
||||||
|
tool_calls: [{ index: 0, id: "call_1", function: { name: "lookup", arguments: "{" } }],
|
||||||
|
}),
|
||||||
|
deltaChunk({
|
||||||
|
tool_calls: [{ index: 0, id: "", function: { name: "", arguments: '\"query\":\"weather\"}' } }],
|
||||||
|
}),
|
||||||
|
deltaChunk({}, "tool_calls"),
|
||||||
|
)
|
||||||
|
const response = yield* LLMClient.generate(
|
||||||
|
LLM.updateRequest(request, {
|
||||||
|
tools: [{ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } }],
|
||||||
|
}),
|
||||||
|
).pipe(Effect.provide(fixedResponse(body)))
|
||||||
|
|
||||||
|
expect(response.toolCalls).toMatchObject([{ id: "call_1", name: "lookup", input: { query: "weather" } }])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("buffers tool call deltas until the function name arrives", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const body = sseEvents(
|
||||||
|
deltaChunk({
|
||||||
|
tool_calls: [{ index: 0, id: "call_1", function: { arguments: "{" } }],
|
||||||
|
}),
|
||||||
|
deltaChunk({
|
||||||
|
tool_calls: [{ index: 0, function: { name: "lookup", arguments: '\"query\":' } }],
|
||||||
|
}),
|
||||||
|
deltaChunk({ tool_calls: [{ index: 0, function: { arguments: '\"weather\"}' } }] }),
|
||||||
|
deltaChunk({}, "tool_calls"),
|
||||||
|
)
|
||||||
|
const response = yield* LLMClient.generate(
|
||||||
|
LLM.updateRequest(request, {
|
||||||
|
tools: [{ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } }],
|
||||||
|
}),
|
||||||
|
).pipe(Effect.provide(fixedResponse(body)))
|
||||||
|
|
||||||
|
expect(response.toolCalls).toMatchObject([{ id: "call_1", name: "lookup", input: { query: "weather" } }])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("fails when a buffered tool call never receives a function name", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const body = sseEvents(
|
||||||
|
deltaChunk({
|
||||||
|
tool_calls: [{ index: 0, id: "call_1", function: { arguments: "{}" } }],
|
||||||
|
}),
|
||||||
|
deltaChunk({}, "tool_calls"),
|
||||||
|
)
|
||||||
|
const error = yield* LLMClient.generate(
|
||||||
|
LLM.updateRequest(request, {
|
||||||
|
tools: [{ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } }],
|
||||||
|
}),
|
||||||
|
).pipe(Effect.provide(fixedResponse(body)), Effect.flip)
|
||||||
|
|
||||||
|
expect(error.message).toContain("OpenAI Chat tool call delta is missing id or name")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("fails a streamed tool call when the provider ends without a finish reason", () =>
|
it.effect("fails a streamed tool call when the provider ends without a finish reason", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const body = sseEvents(
|
const body = sseEvents(
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { describe, expect } from "bun:test"
|
||||||
|
import { Effect } from "effect"
|
||||||
|
import { Image } from "../../src"
|
||||||
|
import { OpenAI } from "../../src/providers"
|
||||||
|
import { recordedTests } from "../recorded-test"
|
||||||
|
|
||||||
|
const model = OpenAI.configure({
|
||||||
|
apiKey: process.env.OPENAI_API_KEY ?? "fixture",
|
||||||
|
image: {
|
||||||
|
providerOptions: {
|
||||||
|
quality: "low",
|
||||||
|
outputFormat: "jpeg",
|
||||||
|
outputCompression: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).image("gpt-image-1-mini")
|
||||||
|
|
||||||
|
const recorded = recordedTests({
|
||||||
|
prefix: "openai-images",
|
||||||
|
provider: "openai",
|
||||||
|
protocol: "openai-images",
|
||||||
|
requires: ["OPENAI_API_KEY"],
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("OpenAI Images recorded", () => {
|
||||||
|
recorded.effect("generates an image", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const response = yield* Image.generate({
|
||||||
|
model,
|
||||||
|
prompt: "A simple flat black circle centered on a plain white background.",
|
||||||
|
size: { width: 1024, height: 1024 },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.images).toHaveLength(1)
|
||||||
|
expect(response.image?.mediaType).toBe("image/jpeg")
|
||||||
|
expect(response.image?.data).toBeInstanceOf(Uint8Array)
|
||||||
|
expect(response.image?.data.length).toBeGreaterThan(0)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { describe, expect } from "bun:test"
|
||||||
|
import { Effect } from "effect"
|
||||||
|
import { LLM, LLMEvent, Message } from "../../src"
|
||||||
|
import { OpenAI } from "../../src/providers"
|
||||||
|
import { recordedTests } from "../recorded-test"
|
||||||
|
|
||||||
|
const openai = OpenAI.configure({
|
||||||
|
apiKey: process.env.OPENAI_API_KEY ?? "fixture",
|
||||||
|
})
|
||||||
|
|
||||||
|
const recorded = recordedTests({
|
||||||
|
prefix: "openai-responses-images",
|
||||||
|
provider: "openai",
|
||||||
|
protocol: "openai-responses",
|
||||||
|
requires: ["OPENAI_API_KEY"],
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("OpenAI Responses image generation recorded", () => {
|
||||||
|
recorded.effect("generates and edits an image with the hosted tool", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const initial = Message.user("Generate a simple flat black triangle centered on a plain white background.")
|
||||||
|
const tools = [
|
||||||
|
OpenAI.imageGeneration({
|
||||||
|
action: "auto",
|
||||||
|
quality: "low",
|
||||||
|
size: "1024x1024",
|
||||||
|
outputFormat: "jpeg",
|
||||||
|
outputCompression: 10,
|
||||||
|
partialImages: 0,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
const response = yield* LLM.generate(
|
||||||
|
LLM.request({
|
||||||
|
model: openai.responses("gpt-5-mini"),
|
||||||
|
messages: [initial],
|
||||||
|
tools,
|
||||||
|
toolChoice: "image_generation",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = response.events.find(LLMEvent.is.toolResult)
|
||||||
|
expect(result).toBeDefined()
|
||||||
|
expect(result?.providerExecuted).toBe(true)
|
||||||
|
expect(result?.result.type).toBe("content")
|
||||||
|
if (result?.result.type !== "content") return
|
||||||
|
expect(result.result.value).toHaveLength(1)
|
||||||
|
expect(result.result.value[0]?.type).toBe("file")
|
||||||
|
if (result.result.value[0]?.type !== "file") return
|
||||||
|
expect(result.result.value[0].mime).toBe("image/jpeg")
|
||||||
|
expect(result.result.value[0].uri.startsWith("data:image/jpeg;base64,")).toBe(true)
|
||||||
|
|
||||||
|
const edited = yield* LLM.generate(
|
||||||
|
LLM.request({
|
||||||
|
model: openai.responses("gpt-5-mini"),
|
||||||
|
messages: [initial, response.message, Message.user("Now make the triangle blue.")],
|
||||||
|
tools,
|
||||||
|
toolChoice: "image_generation",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const editedResult = edited.events.find(LLMEvent.is.toolResult)
|
||||||
|
expect(editedResult?.result.type).toBe("content")
|
||||||
|
if (editedResult?.result.type !== "content") return
|
||||||
|
expect(editedResult.result.value[0]?.type).toBe("file")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, expect } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
import { ConfigProvider, Effect, Layer, Stream } from "effect"
|
import { ConfigProvider, Effect, Layer, Stream } from "effect"
|
||||||
import { Headers, HttpClientRequest } from "effect/unstable/http"
|
import { Headers, HttpClientRequest } from "effect/unstable/http"
|
||||||
import { LLM, LLMError, LLMEvent, Message, Model, ToolCallPart, Usage } from "../../src"
|
import { LLM, LLMError, LLMEvent, Message, Model, ToolCallPart, ToolResultPart, Usage } from "../../src"
|
||||||
import { Auth, LLMClient, RequestExecutor, WebSocketExecutor } from "../../src/route"
|
import { Auth, LLMClient, RequestExecutor, WebSocketExecutor } from "../../src/route"
|
||||||
import * as Azure from "../../src/providers/azure"
|
import * as Azure from "../../src/providers/azure"
|
||||||
import * as OpenAI from "../../src/providers/openai"
|
import * as OpenAI from "../../src/providers/openai"
|
||||||
@@ -58,6 +58,39 @@ describe("OpenAI Responses route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("lowers the hosted OpenAI image generation tool", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||||
|
LLM.request({
|
||||||
|
model,
|
||||||
|
prompt: "Show me a rooftop garden.",
|
||||||
|
tools: [OpenAI.imageGeneration({ action: "generate", quality: "high", size: "1024x1024" })],
|
||||||
|
toolChoice: "image_generation",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.tools).toEqual([
|
||||||
|
{ type: "image_generation", action: "generate", quality: "high", size: "1024x1024" },
|
||||||
|
])
|
||||||
|
expect(prepared.body.tool_choice).toEqual({ type: "image_generation" })
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("rejects invalid hosted image generation options locally", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const error = yield* LLMClient.prepare(
|
||||||
|
LLM.request({
|
||||||
|
model,
|
||||||
|
prompt: "Show me a rooftop garden.",
|
||||||
|
tools: [OpenAI.imageGeneration({ outputCompression: -1, partialImages: 4, size: "bogus" })],
|
||||||
|
}),
|
||||||
|
).pipe(Effect.flip)
|
||||||
|
|
||||||
|
expect(error.reason._tag).toBe("InvalidRequest")
|
||||||
|
expect(error.message).toContain("image generation tool options are invalid")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("lowers semantic service tier options", () =>
|
it.effect("lowers semantic service tier options", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const input = LLM.updateRequest(request, { providerOptions: { openai: { serviceTier: "priority" } } })
|
const input = LLM.updateRequest(request, { providerOptions: { openai: { serviceTier: "priority" } } })
|
||||||
@@ -1103,6 +1136,48 @@ describe("OpenAI Responses route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("continues stateless hosted image generation with the generated image", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const imageTool = OpenAI.imageGeneration({ action: "edit" })
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||||
|
LLM.request({
|
||||||
|
model,
|
||||||
|
messages: [
|
||||||
|
Message.user("Generate a black triangle."),
|
||||||
|
Message.assistant([
|
||||||
|
ToolCallPart.make({
|
||||||
|
id: "ig_1",
|
||||||
|
name: "image_generation",
|
||||||
|
input: {},
|
||||||
|
providerExecuted: true,
|
||||||
|
providerMetadata: { openai: { itemId: "ig_1" } },
|
||||||
|
}),
|
||||||
|
ToolResultPart.make({
|
||||||
|
id: "ig_1",
|
||||||
|
name: "image_generation",
|
||||||
|
result: {
|
||||||
|
type: "content",
|
||||||
|
value: [{ type: "file", uri: "data:image/png;base64,AQID", mime: "image/png" }],
|
||||||
|
},
|
||||||
|
providerExecuted: true,
|
||||||
|
providerMetadata: { openai: { itemId: "ig_1" } },
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
Message.user("Make it blue."),
|
||||||
|
],
|
||||||
|
tools: [imageTool],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.store).toBe(false)
|
||||||
|
expect(prepared.body.input).toEqual([
|
||||||
|
{ role: "user", content: [{ type: "input_text", text: "Generate a black triangle." }] },
|
||||||
|
{ role: "user", content: [{ type: "input_image", image_url: "data:image/png;base64,AQID" }] },
|
||||||
|
{ role: "user", content: [{ type: "input_text", text: "Make it blue." }] },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("joins streamed summary blocks into one continuation reasoning item", () =>
|
it.effect("joins streamed summary blocks into one continuation reasoning item", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||||
@@ -1259,59 +1334,66 @@ describe("OpenAI Responses route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("finalizes an empty function call when the terminal response omits output_item.done", () =>
|
it.effect("emits malformed final function arguments as an unexecuted tool error", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const body = sseEvents(
|
const body = sseEvents(
|
||||||
{
|
{
|
||||||
type: "response.output_item.added",
|
type: "response.output_item.added",
|
||||||
item: { type: "function_call", id: "item_1", call_id: "call_1", name: "patch", arguments: "" },
|
item: { type: "function_call", id: "item_1", call_id: "call_1", name: "lookup", arguments: "" },
|
||||||
|
},
|
||||||
|
{ type: "response.function_call_arguments.delta", item_id: "item_1", delta: '{"query":"streamed"}' },
|
||||||
|
{
|
||||||
|
type: "response.output_item.done",
|
||||||
|
item: {
|
||||||
|
type: "function_call",
|
||||||
|
id: "item_1",
|
||||||
|
call_id: "call_1",
|
||||||
|
name: "lookup",
|
||||||
|
arguments: '{"query":"partial',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{ type: "response.completed", response: { usage: { input_tokens: 5, output_tokens: 1 } } },
|
{ type: "response.completed", response: { usage: { input_tokens: 5, output_tokens: 1 } } },
|
||||||
)
|
)
|
||||||
const response = yield* LLMClient.generate(
|
const response = yield* LLMClient.generate(
|
||||||
LLM.updateRequest(request, {
|
LLM.updateRequest(request, {
|
||||||
tools: [{ name: "patch", description: "Apply a patch", inputSchema: { type: "object" } }],
|
tools: [{ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } }],
|
||||||
}),
|
}),
|
||||||
).pipe(Effect.provide(fixedResponse(body)))
|
).pipe(Effect.provide(fixedResponse(body)))
|
||||||
|
|
||||||
expect(response.events).toContainEqual({
|
expect(response.events.find(LLMEvent.is.toolInputError)).toEqual({
|
||||||
type: "tool-call",
|
type: "tool-input-error",
|
||||||
id: "call_1",
|
id: "call_1",
|
||||||
name: "patch",
|
name: "lookup",
|
||||||
input: {},
|
raw: '{"query":"partial',
|
||||||
providerExecuted: undefined,
|
|
||||||
providerMetadata: { openai: { itemId: "item_1" } },
|
|
||||||
})
|
})
|
||||||
expect(response.finishReason).toBe("tool-calls")
|
expect(response.finishReason).toBe("tool-calls")
|
||||||
|
expect(response.events.some(LLMEvent.is.toolCall)).toBeFalse()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("rejects an incomplete pending function call", () =>
|
it.effect("settles malformed function arguments when output_item.added is absent", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const body = sseEvents(
|
const body = sseEvents(
|
||||||
{
|
{
|
||||||
type: "response.output_item.added",
|
type: "response.output_item.done",
|
||||||
item: { type: "function_call", id: "item_1", call_id: "call_1", name: "patch", arguments: "" },
|
item: {
|
||||||
},
|
type: "function_call",
|
||||||
{
|
id: "item_1",
|
||||||
type: "response.incomplete",
|
call_id: "call_1",
|
||||||
response: { incomplete_details: { reason: "max_output_tokens" } },
|
name: "lookup",
|
||||||
|
arguments: '{"query":"partial',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
{ type: "response.completed", response: { usage: { input_tokens: 5, output_tokens: 1 } } },
|
||||||
)
|
)
|
||||||
const events: LLMEvent[] = []
|
const response = yield* LLMClient.generate(request).pipe(Effect.provide(fixedResponse(body)))
|
||||||
const error = yield* LLMClient.stream(
|
|
||||||
LLM.updateRequest(request, {
|
|
||||||
tools: [{ name: "patch", description: "Apply a patch", inputSchema: { type: "object" } }],
|
|
||||||
}),
|
|
||||||
).pipe(
|
|
||||||
Stream.runForEach((event) => Effect.sync(() => events.push(event))),
|
|
||||||
Effect.flip,
|
|
||||||
Effect.provide(fixedResponse(body)),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(events.filter(LLMEvent.is.toolCall)).toEqual([])
|
expect(response.events.find(LLMEvent.is.toolInputError)).toMatchObject({
|
||||||
expect(error.reason).toMatchObject({ _tag: "InvalidProviderOutput" })
|
id: "call_1",
|
||||||
expect(error.message).toContain("OpenAI Responses response incomplete with pending tool calls")
|
name: "lookup",
|
||||||
|
raw: '{"query":"partial',
|
||||||
|
})
|
||||||
|
expect(response.finishReason).toBe("tool-calls")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1354,6 +1436,59 @@ describe("OpenAI Responses route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("decodes image generation output as image content", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const item = {
|
||||||
|
type: "image_generation_call",
|
||||||
|
id: "ig_1",
|
||||||
|
status: "completed",
|
||||||
|
result: "AQID",
|
||||||
|
}
|
||||||
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{ type: "response.output_item.done", item },
|
||||||
|
{ type: "response.completed", response: { usage: { input_tokens: 5, output_tokens: 1 } } },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.events.find(LLMEvent.is.toolResult)).toMatchObject({
|
||||||
|
id: "ig_1",
|
||||||
|
name: "image_generation",
|
||||||
|
providerExecuted: true,
|
||||||
|
result: {
|
||||||
|
type: "content",
|
||||||
|
value: [{ type: "file", uri: "data:image/png;base64,AQID", mime: "image/png" }],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("rejects malformed image generation base64", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const error = yield* LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{
|
||||||
|
type: "response.output_item.done",
|
||||||
|
item: { type: "image_generation_call", id: "ig_bad", status: "completed", result: "%%%" },
|
||||||
|
},
|
||||||
|
{ type: "response.completed", response: {} },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Effect.flip,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(error.reason._tag).toBe("InvalidProviderOutput")
|
||||||
|
expect(error.message).toContain("invalid image base64")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("decodes code_interpreter_call as provider-executed events with code input", () =>
|
it.effect("decodes code_interpreter_call as provider-executed events with code input", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const item = {
|
const item = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, expect } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { LLM } from "../../src"
|
import { LLM, Message } from "../../src"
|
||||||
import { LLMClient } from "../../src/route"
|
import { LLMClient } from "../../src/route"
|
||||||
import * as OpenRouter from "../../src/providers/openrouter"
|
import * as OpenRouter from "../../src/providers/openrouter"
|
||||||
import { it } from "../lib/effect"
|
import { it } from "../lib/effect"
|
||||||
@@ -53,4 +53,102 @@ describe("OpenRouter", () => {
|
|||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("preserves manually supplied reasoning details", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const details = [
|
||||||
|
{ type: "reasoning.text", text: "Think", format: "anthropic-claude-v1", index: 0 },
|
||||||
|
{ type: "reasoning.text", text: "ing", format: "anthropic-claude-v1", index: 0 },
|
||||||
|
{ type: "reasoning.text", signature: "signed", format: "anthropic-claude-v1", index: 0 },
|
||||||
|
{ type: "reasoning.encrypted", data: "opaque", format: "openai-responses-v1", index: 1 },
|
||||||
|
]
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenRouter.OpenRouterBody>(
|
||||||
|
LLM.request({
|
||||||
|
model: OpenRouter.configure({ apiKey: "test-key" }).model("anthropic/claude-sonnet-4.6"),
|
||||||
|
messages: [
|
||||||
|
Message.assistant([
|
||||||
|
{
|
||||||
|
type: "reasoning",
|
||||||
|
text: "Thinking",
|
||||||
|
providerMetadata: { openai: { reasoningField: "reasoning", reasoningDetails: details } },
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.messages).toEqual([
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: null,
|
||||||
|
reasoning: "Thinking",
|
||||||
|
reasoning_details: details,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("preserves opaque and duplicate continuation details", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const details = [
|
||||||
|
{ type: "reasoning.future", format: "provider-v2", state: { opaque: true } },
|
||||||
|
{ type: "reasoning.encrypted", id: "state", data: "opaque" },
|
||||||
|
{ type: "reasoning.encrypted", id: "state", data: "opaque" },
|
||||||
|
]
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenRouter.OpenRouterBody>(
|
||||||
|
LLM.request({
|
||||||
|
model: OpenRouter.configure({ apiKey: "test-key" }).model("anthropic/claude-sonnet-4.6"),
|
||||||
|
messages: [
|
||||||
|
Message.assistant({
|
||||||
|
type: "reasoning",
|
||||||
|
text: "Thinking",
|
||||||
|
providerMetadata: { openai: { reasoningField: "reasoning", reasoningDetails: details } },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.messages).toEqual([
|
||||||
|
{ role: "assistant", content: null, reasoning: "Thinking", reasoning_details: details },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("does not merge distinct adjacent reasoning text blocks", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const details = [
|
||||||
|
{ type: "reasoning.text", id: "first", index: 0, text: "A", opaque: "first" },
|
||||||
|
{ type: "reasoning.text", id: "second", index: 1, text: "B", opaque: "second" },
|
||||||
|
]
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenRouter.OpenRouterBody>(
|
||||||
|
LLM.request({
|
||||||
|
model: OpenRouter.configure({ apiKey: "test-key" }).model("anthropic/claude-sonnet-4.6"),
|
||||||
|
messages: [
|
||||||
|
Message.assistant({
|
||||||
|
type: "reasoning",
|
||||||
|
text: "AB",
|
||||||
|
providerMetadata: { openai: { reasoningField: "reasoning", reasoningDetails: details } },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.messages).toEqual([
|
||||||
|
{ role: "assistant", content: null, reasoning: "AB", reasoning_details: details },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("omits scalar reasoning without continuation details", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenRouter.OpenRouterBody>(
|
||||||
|
LLM.request({
|
||||||
|
model: OpenRouter.configure({ apiKey: "test-key" }).model("anthropic/claude-sonnet-4.6"),
|
||||||
|
messages: [Message.assistant({ type: "reasoning", text: "Thinking" })],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.messages).toEqual([{ role: "assistant", content: null }])
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -120,29 +120,8 @@ export const runWeatherToolLoop = (request: LLMRequest) =>
|
|||||||
throw new Error("Weather tool loop exceeded 10 steps")
|
throw new Error("Weather tool loop exceeded 10 steps")
|
||||||
})
|
})
|
||||||
|
|
||||||
const assistantContent = (events: ReadonlyArray<LLMEvent>) => {
|
const assistantContent = (events: ReadonlyArray<LLMEvent>) =>
|
||||||
const content: ContentPart[] = []
|
events.reduce(LLMResponse.reduce, LLMResponse.empty()).message.content
|
||||||
for (const event of events) {
|
|
||||||
if (event.type === "text-delta" || event.type === "reasoning-delta") {
|
|
||||||
const type = event.type === "text-delta" ? "text" : "reasoning"
|
|
||||||
const last = content.at(-1)
|
|
||||||
if (last?.type === type) {
|
|
||||||
content[content.length - 1] = { ...last, text: `${last.text}${event.text}` }
|
|
||||||
} else {
|
|
||||||
content.push({ type, text: event.text })
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (event.type === "text-end" || event.type === "reasoning-end") {
|
|
||||||
const type = event.type === "text-end" ? "text" : "reasoning"
|
|
||||||
const last = content.at(-1)
|
|
||||||
if (last?.type === type) content[content.length - 1] = { ...last, providerMetadata: event.providerMetadata }
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (event.type === "tool-call") content.push(event)
|
|
||||||
}
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
export const expectFinish = (
|
export const expectFinish = (
|
||||||
events: ReadonlyArray<LLMEvent>,
|
events: ReadonlyArray<LLMEvent>,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { Layer } from "effect"
|
|||||||
import * as path from "node:path"
|
import * as path from "node:path"
|
||||||
import { fileURLToPath } from "node:url"
|
import { fileURLToPath } from "node:url"
|
||||||
import { LLMClient, RequestExecutor, WebSocketExecutor } from "../src/route"
|
import { LLMClient, RequestExecutor, WebSocketExecutor } from "../src/route"
|
||||||
|
import { ImageClient } from "../src/image-client"
|
||||||
|
import type { Service as ImageClientService } from "../src/image-client"
|
||||||
import type { Service as LLMClientService } from "../src/route/client"
|
import type { Service as LLMClientService } from "../src/route/client"
|
||||||
import type { Service as RequestExecutorService } from "../src/route/executor"
|
import type { Service as RequestExecutorService } from "../src/route/executor"
|
||||||
import type { Service as WebSocketExecutorService } from "../src/route/transport/websocket"
|
import type { Service as WebSocketExecutorService } from "../src/route/transport/websocket"
|
||||||
@@ -15,7 +17,7 @@ import {
|
|||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
const FIXTURES_DIR = path.resolve(__dirname, "fixtures", "recordings")
|
const FIXTURES_DIR = path.resolve(__dirname, "fixtures", "recordings")
|
||||||
|
|
||||||
type RecordedEnv = RequestExecutorService | WebSocketExecutorService | LLMClientService
|
type RecordedEnv = RequestExecutorService | WebSocketExecutorService | LLMClientService | ImageClientService
|
||||||
|
|
||||||
type RecordedTestsOptions = RecordedGroupOptions & {
|
type RecordedTestsOptions = RecordedGroupOptions & {
|
||||||
readonly options?: HttpRecorder.RecorderOptions
|
readonly options?: HttpRecorder.RecorderOptions
|
||||||
@@ -81,6 +83,10 @@ export const recordedTests = (options: RecordedTestsOptions) =>
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
const deps = Layer.mergeAll(requestExecutor, WebSocketExecutor.layer)
|
const deps = Layer.mergeAll(requestExecutor, WebSocketExecutor.layer)
|
||||||
return Layer.mergeAll(deps, LLMClient.layer.pipe(Layer.provide(deps)))
|
return Layer.mergeAll(
|
||||||
|
deps,
|
||||||
|
LLMClient.layer.pipe(Layer.provide(deps)),
|
||||||
|
ImageClient.layer.pipe(Layer.provide(deps)),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -95,4 +95,19 @@ describe("LLMResponse reducer", () => {
|
|||||||
{ type: "tool-call", id: "call_1", name: "lookup", input: { query: "weather" } },
|
{ type: "tool-call", id: "call_1", name: "lookup", input: { query: "weather" } },
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("clears malformed tool input without appending an executable call", () => {
|
||||||
|
const state = reduce([
|
||||||
|
LLMEvent.toolInputStart({ id: "call_1", name: "lookup" }),
|
||||||
|
LLMEvent.toolInputDelta({ id: "call_1", name: "lookup", text: '{"query":"partial' }),
|
||||||
|
LLMEvent.toolInputError({
|
||||||
|
id: "call_1",
|
||||||
|
name: "lookup",
|
||||||
|
raw: '{"query":"partial',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(state.toolInputs).toEqual({})
|
||||||
|
expect(state.message.content).toEqual([])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -36,6 +36,33 @@ describe("ToolStream", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("keeps accumulated identity when later deltas contain empty strings", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const first = ToolStream.appendOrStart(
|
||||||
|
ADAPTER,
|
||||||
|
ToolStream.empty<number>(),
|
||||||
|
0,
|
||||||
|
{ id: "call_1", name: "lookup", text: '{"query"' },
|
||||||
|
"missing tool",
|
||||||
|
)
|
||||||
|
if (ToolStream.isError(first)) return yield* first
|
||||||
|
const second = ToolStream.appendOrStart(
|
||||||
|
ADAPTER,
|
||||||
|
first.tools,
|
||||||
|
0,
|
||||||
|
{ id: "", name: "", text: ':"weather"}' },
|
||||||
|
"missing tool",
|
||||||
|
)
|
||||||
|
if (ToolStream.isError(second)) return yield* second
|
||||||
|
const finished = yield* ToolStream.finish(ADAPTER, second.tools, 0)
|
||||||
|
|
||||||
|
expect(finished.events).toEqual([
|
||||||
|
{ type: "tool-input-end", id: "call_1", name: "lookup" },
|
||||||
|
{ type: "tool-call", id: "call_1", name: "lookup", input: { query: "weather" } },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("fails appendExisting when the provider skipped the tool start", () =>
|
it.effect("fails appendExisting when the provider skipped the tool start", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const error = ToolStream.appendExisting(ADAPTER, ToolStream.empty<number>(), 0, "{}", "missing tool")
|
const error = ToolStream.appendExisting(ADAPTER, ToolStream.empty<number>(), 0, "{}", "missing tool")
|
||||||
@@ -64,6 +91,73 @@ describe("ToolStream", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("finalizes malformed local input as a non-executable tool error", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const tools = ToolStream.start(ToolStream.empty<string>(), "item_1", {
|
||||||
|
id: "call_1",
|
||||||
|
name: "lookup",
|
||||||
|
input: '{"query":"partial',
|
||||||
|
})
|
||||||
|
const finished = yield* ToolStream.finish(ADAPTER, tools, "item_1")
|
||||||
|
|
||||||
|
expect(finished).toEqual({
|
||||||
|
tools: {},
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
type: "tool-input-error",
|
||||||
|
id: "call_1",
|
||||||
|
name: "lookup",
|
||||||
|
raw: '{"query":"partial',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("preserves valid siblings when one parallel input is malformed", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const valid = ToolStream.start(ToolStream.empty<number>(), 0, {
|
||||||
|
id: "call_valid",
|
||||||
|
name: "lookup",
|
||||||
|
input: '{"query":"weather"}',
|
||||||
|
})
|
||||||
|
const tools = ToolStream.start(valid, 1, {
|
||||||
|
id: "call_invalid",
|
||||||
|
name: "lookup",
|
||||||
|
input: '{"query":"partial',
|
||||||
|
})
|
||||||
|
const finished = yield* ToolStream.finishAll(ADAPTER, tools)
|
||||||
|
|
||||||
|
expect(finished).toEqual({
|
||||||
|
tools: {},
|
||||||
|
events: [
|
||||||
|
{ type: "tool-input-end", id: "call_valid", name: "lookup" },
|
||||||
|
{ type: "tool-call", id: "call_valid", name: "lookup", input: { query: "weather" } },
|
||||||
|
{
|
||||||
|
type: "tool-input-error",
|
||||||
|
id: "call_invalid",
|
||||||
|
name: "lookup",
|
||||||
|
raw: '{"query":"partial',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("keeps malformed provider-executed input terminal", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const tools = ToolStream.start(ToolStream.empty<string>(), "item_1", {
|
||||||
|
id: "call_1",
|
||||||
|
name: "web_search",
|
||||||
|
input: '{"query":"partial',
|
||||||
|
providerExecuted: true,
|
||||||
|
})
|
||||||
|
const result = yield* Effect.exit(ToolStream.finish(ADAPTER, tools, "item_1"))
|
||||||
|
|
||||||
|
expect(result._tag).toBe("Failure")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("preserves providerExecuted and clears all tools", () =>
|
it.effect("preserves providerExecuted and clears all tools", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const first: ToolStream.State<number> = ToolStream.start(ToolStream.empty<number>(), 0, {
|
const first: ToolStream.State<number> = ToolStream.start(ToolStream.empty<number>(), 0, {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.17.20",
|
"version": "1.18.3",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
|
||||||
import { getFilename } from "@opencode-ai/core/util/path"
|
import { getFilename } from "@opencode-ai/core/util/path"
|
||||||
|
import type { GlobalSession, Project } from "@opencode-ai/sdk/v2/client"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useNavigate } from "@solidjs/router"
|
|
||||||
import { createMemo, onCleanup } from "solid-js"
|
import { createMemo, onCleanup } from "solid-js"
|
||||||
import { useCommand, type CommandOption } from "@/context/command"
|
import { commandPaletteOptions, useCommand, type CommandOption } from "@/context/command"
|
||||||
import { useFile } from "@/context/file"
|
import { useFile } from "@/context/file"
|
||||||
|
import { useGlobal } from "@/context/global"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout, type LocalProject } from "@/context/layout"
|
||||||
import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
|
import { ServerConnection } from "@/context/server"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
|
import { useTabs } from "@/context/tabs"
|
||||||
|
import { displayName, projectForSession } from "@/pages/layout/helpers"
|
||||||
import { createSessionTabs } from "@/pages/session/helpers"
|
import { createSessionTabs } from "@/pages/session/helpers"
|
||||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||||
import { decode64 } from "@/utils/base64"
|
|
||||||
|
|
||||||
export type CommandPaletteEntry = {
|
export type CommandPaletteEntry = {
|
||||||
id: string
|
id: string
|
||||||
@@ -24,6 +25,8 @@ export type CommandPaletteEntry = {
|
|||||||
path?: string
|
path?: string
|
||||||
directory?: string
|
directory?: string
|
||||||
sessionID?: string
|
sessionID?: string
|
||||||
|
server?: ServerConnection.Key
|
||||||
|
project?: LocalProject
|
||||||
archived?: number
|
archived?: number
|
||||||
updated?: number
|
updated?: number
|
||||||
}
|
}
|
||||||
@@ -75,28 +78,25 @@ export function createCommandPaletteFileOpener(onOpenFile?: (path: string) => vo
|
|||||||
|
|
||||||
export function createCommandPaletteModel(props: { filesOnly?: () => boolean; onOpenFile?: (path: string) => void }) {
|
export function createCommandPaletteModel(props: { filesOnly?: () => boolean; onOpenFile?: (path: string) => void }) {
|
||||||
const command = useCommand()
|
const command = useCommand()
|
||||||
|
const global = useGlobal()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const layout = useLayout()
|
|
||||||
const file = useFile()
|
const file = useFile()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const navigate = useNavigate()
|
|
||||||
const serverSDK = useServerSDK()()
|
const serverSDK = useServerSDK()()
|
||||||
const serverSync = useServerSync()
|
const serverCtx = global.ensureServerCtx(serverSDK.server)
|
||||||
const { params, tabs } = useSessionLayout()
|
const appTabs = useTabs()
|
||||||
|
const { tabs: sessionTabs } = useSessionLayout()
|
||||||
const openFile = createCommandPaletteFileOpener(props.onOpenFile)
|
const openFile = createCommandPaletteFileOpener(props.onOpenFile)
|
||||||
const state = { cleanup: undefined as (() => void) | void, committed: false }
|
const state = { cleanup: undefined as (() => void) | void, committed: false }
|
||||||
const filesOnly = () => props.filesOnly?.() ?? false
|
const filesOnly = () => props.filesOnly?.() ?? false
|
||||||
|
|
||||||
const allowedCommands = createMemo(() => {
|
const allowedCommands = createMemo(() => {
|
||||||
if (filesOnly()) return []
|
if (filesOnly()) return []
|
||||||
return command.options.filter(
|
return commandPaletteOptions(command.options)
|
||||||
(option) =>
|
|
||||||
!option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open",
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
const commandEntries = createMemo(() => {
|
const commandEntries = createMemo(() => {
|
||||||
const category = language.t("palette.group.commands")
|
const category = language.t("palette.group.commands")
|
||||||
return allowedCommands().map((option) => createCommandEntry(option, category))
|
return allowedCommands().map((option) => createCommandPaletteCommandEntry(option, category))
|
||||||
})
|
})
|
||||||
const preferredCommandEntries = createMemo(() => {
|
const preferredCommandEntries = createMemo(() => {
|
||||||
const all = allowedCommands()
|
const all = allowedCommands()
|
||||||
@@ -105,11 +105,11 @@ export function createCommandPaletteModel(props: { filesOnly?: () => boolean; on
|
|||||||
const base = picked.length ? picked : all.slice(0, ENTRY_LIMIT)
|
const base = picked.length ? picked : all.slice(0, ENTRY_LIMIT)
|
||||||
const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
|
const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
|
||||||
const category = language.t("palette.group.commands")
|
const category = language.t("palette.group.commands")
|
||||||
return sorted.map((option) => createCommandEntry(option, category))
|
return sorted.map((option) => createCommandPaletteCommandEntry(option, category))
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabState = createSessionTabs({
|
const tabState = createSessionTabs({
|
||||||
tabs,
|
tabs: sessionTabs,
|
||||||
pathFromTab: file.pathFromTab,
|
pathFromTab: file.pathFromTab,
|
||||||
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
|
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
|
||||||
})
|
})
|
||||||
@@ -140,36 +140,12 @@ export function createCommandPaletteModel(props: { filesOnly?: () => boolean; on
|
|||||||
.map((path) => createCommandPaletteFileEntry(path, category))
|
.map((path) => createCommandPaletteFileEntry(path, category))
|
||||||
})
|
})
|
||||||
|
|
||||||
const projectDirectory = createMemo(() => decode64(params.dir) ?? "")
|
const sessions = createServerSessionEntries({
|
||||||
const project = createMemo(() => {
|
server: ServerConnection.key(serverSDK.server),
|
||||||
const directory = projectDirectory()
|
opened: serverCtx.projects.list,
|
||||||
if (!directory) return undefined
|
stored: () => serverCtx.sync.data.project,
|
||||||
return layout.projects.list().find((item) => item.worktree === directory || item.sandboxes?.includes(directory))
|
load: (search, signal) =>
|
||||||
})
|
serverSDK.client.experimental.session.list({ roots: true, search, limit: 50 }, { signal }),
|
||||||
const workspaces = createMemo(() => {
|
|
||||||
const directory = projectDirectory()
|
|
||||||
const current = project()
|
|
||||||
if (!current) return directory ? [directory] : []
|
|
||||||
const dirs = [current.worktree, ...(current.sandboxes ?? [])]
|
|
||||||
if (directory && !dirs.includes(directory)) return [...dirs, directory]
|
|
||||||
return dirs
|
|
||||||
})
|
|
||||||
const homedir = createMemo(() => serverSync().data.path.home)
|
|
||||||
const sessions = createSessionEntries({
|
|
||||||
workspaces,
|
|
||||||
label: (directory) => {
|
|
||||||
const current = project()
|
|
||||||
const kind =
|
|
||||||
current && directory === current.worktree
|
|
||||||
? language.t("workspace.type.local")
|
|
||||||
: language.t("workspace.type.sandbox")
|
|
||||||
const [store] = serverSync().child(directory, { bootstrap: false })
|
|
||||||
const home = homedir()
|
|
||||||
const path = home ? directory.replace(home, "~") : directory
|
|
||||||
const name = store.vcs?.branch ?? getFilename(directory)
|
|
||||||
return `${kind} : ${name || path}`
|
|
||||||
},
|
|
||||||
load: (directory) => serverSDK.client.session.list({ directory, roots: true }),
|
|
||||||
untitled: () => language.t("command.session.new"),
|
untitled: () => language.t("command.session.new"),
|
||||||
category: () => language.t("command.category.session"),
|
category: () => language.t("command.category.session"),
|
||||||
})
|
})
|
||||||
@@ -191,8 +167,17 @@ export function createCommandPaletteModel(props: { filesOnly?: () => boolean; on
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (item.type === "session") {
|
if (item.type === "session") {
|
||||||
if (!item.directory || !item.sessionID) return
|
if (!item.sessionID || !item.server) return
|
||||||
navigate(`/${base64Encode(item.directory)}/session/${item.sessionID}`)
|
const directory = item.project?.worktree ?? item.directory
|
||||||
|
if (directory) {
|
||||||
|
serverCtx.projects.open(directory)
|
||||||
|
serverCtx.projects.touch(directory)
|
||||||
|
}
|
||||||
|
const tab = appTabs.addSessionTab({
|
||||||
|
server: item.server,
|
||||||
|
sessionId: item.sessionID,
|
||||||
|
})
|
||||||
|
appTabs.select(tab)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!item.path) return
|
if (!item.path) return
|
||||||
@@ -218,7 +203,7 @@ export function createCommandPaletteModel(props: { filesOnly?: () => boolean; on
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCommandEntry(option: CommandOption, category: string): CommandPaletteEntry {
|
export function createCommandPaletteCommandEntry(option: CommandOption, category: string): CommandPaletteEntry {
|
||||||
return {
|
return {
|
||||||
id: "command:" + option.id,
|
id: "command:" + option.id,
|
||||||
type: "command",
|
type: "command",
|
||||||
@@ -230,96 +215,65 @@ function createCommandEntry(option: CommandOption, category: string): CommandPal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSessionEntries(props: {
|
export function createServerSessionEntries(props: {
|
||||||
workspaces: () => string[]
|
server: ServerConnection.Key
|
||||||
label: (directory: string) => string
|
opened: () => LocalProject[]
|
||||||
load: (directory: string) => ReturnType<ServerSDK["client"]["session"]["list"]>
|
stored: () => Project[]
|
||||||
|
load: (search: string, signal: AbortSignal) => Promise<{ data?: GlobalSession[] }>
|
||||||
untitled: () => string
|
untitled: () => string
|
||||||
category: () => string
|
category: () => string
|
||||||
}) {
|
}) {
|
||||||
const state: {
|
let abort: AbortController | undefined
|
||||||
token: number
|
|
||||||
inflight: Promise<CommandPaletteEntry[]> | undefined
|
|
||||||
cached: CommandPaletteEntry[] | undefined
|
|
||||||
} = { token: 0, inflight: undefined, cached: undefined }
|
|
||||||
|
|
||||||
return (text: string) => {
|
onCleanup(() => abort?.abort())
|
||||||
if (!text.trim()) {
|
|
||||||
state.token += 1
|
return async (text: string): Promise<CommandPaletteEntry[]> => {
|
||||||
state.inflight = undefined
|
const search = text.trim()
|
||||||
state.cached = undefined
|
if (!search) {
|
||||||
return [] as CommandPaletteEntry[]
|
abort?.abort()
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
if (state.cached) return state.cached
|
abort?.abort()
|
||||||
if (state.inflight) return state.inflight
|
const current = new AbortController()
|
||||||
|
abort = current
|
||||||
const current = state.token
|
await new Promise<void>((resolve) => {
|
||||||
const dirs = props.workspaces()
|
const timer = setTimeout(resolve, 100)
|
||||||
if (dirs.length === 0) return [] as CommandPaletteEntry[]
|
current.signal.addEventListener(
|
||||||
|
"abort",
|
||||||
state.inflight = Promise.all(
|
() => {
|
||||||
dirs.map((directory) => {
|
clearTimeout(timer)
|
||||||
const description = props.label(directory)
|
resolve()
|
||||||
return props
|
},
|
||||||
.load(directory)
|
{ once: true },
|
||||||
.then((result) =>
|
)
|
||||||
(result.data ?? [])
|
})
|
||||||
.filter((session) => !!session?.id)
|
if (current.signal.aborted) return []
|
||||||
.map((session) => ({
|
const opened = props.opened()
|
||||||
id: session.id,
|
const openedByID = new Map(opened.flatMap((project) => (project.id ? [[project.id, project] as const] : [])))
|
||||||
title: session.title ?? props.untitled(),
|
const stored = props.stored().map((project) => ({ ...project, expanded: false }))
|
||||||
description,
|
const storedByID = new Map(stored.map((project) => [project.id, project] as const))
|
||||||
directory,
|
return props
|
||||||
archived: session.time?.archived,
|
.load(search, current.signal)
|
||||||
updated: session.time?.updated,
|
.then((result) =>
|
||||||
})),
|
(result.data ?? [])
|
||||||
)
|
.filter((session) => !session.time.archived)
|
||||||
.catch(() => [] as SessionEntryInput[])
|
.map((session) => {
|
||||||
}),
|
const project =
|
||||||
)
|
projectForSession(session, opened, openedByID) ?? projectForSession(session, stored, storedByID)
|
||||||
.then((results) => {
|
return {
|
||||||
if (state.token !== current) return [] as CommandPaletteEntry[]
|
id: `session:${props.server}:${session.id}`,
|
||||||
const seen = new Set<string>()
|
type: "session" as const,
|
||||||
const next = results
|
title: session.title || props.untitled(),
|
||||||
.flat()
|
description: project ? displayName(project) : session.project?.name || getFilename(session.directory),
|
||||||
.filter((item) => {
|
category: props.category(),
|
||||||
const key = `${item.directory}:${item.id}`
|
directory: session.directory,
|
||||||
if (seen.has(key)) return false
|
sessionID: session.id,
|
||||||
seen.add(key)
|
server: props.server,
|
||||||
return true
|
project,
|
||||||
})
|
updated: session.time.updated,
|
||||||
.map((item) => createSessionEntry(item, props.category()))
|
}
|
||||||
state.cached = next
|
}),
|
||||||
return next
|
)
|
||||||
})
|
|
||||||
.catch(() => [] as CommandPaletteEntry[])
|
.catch(() => [] as CommandPaletteEntry[])
|
||||||
.finally(() => {
|
|
||||||
state.inflight = undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
return state.inflight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type SessionEntryInput = {
|
|
||||||
directory: string
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
archived?: number
|
|
||||||
updated?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSessionEntry(input: SessionEntryInput, category: string): CommandPaletteEntry {
|
|
||||||
return {
|
|
||||||
id: `session:${input.directory}:${input.id}`,
|
|
||||||
type: "session",
|
|
||||||
title: input.title,
|
|
||||||
description: input.description,
|
|
||||||
category,
|
|
||||||
directory: input.directory,
|
|
||||||
sessionID: input.id,
|
|
||||||
archived: input.archived,
|
|
||||||
updated: input.updated,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,20 @@ import { Dialog, DialogBody } from "@opencode-ai/ui/v2/dialog-v2"
|
|||||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
import { Icon } from "@opencode-ai/ui/v2/icon"
|
||||||
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
||||||
import { createEffect, createMemo, createResource, createSignal, For, Match, Show, Switch } from "solid-js"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { formatKeybindParts } from "@/context/command"
|
import { createEffect, createMemo, createResource, createSignal, For, Match, onCleanup, Show, Switch } from "solid-js"
|
||||||
|
import { commandPaletteOptions, formatKeybindParts, useCommand } from "@/context/command"
|
||||||
|
import { useGlobal } from "@/context/global"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { ServerConnection } from "@/context/server"
|
||||||
|
import { useTabs } from "@/context/tabs"
|
||||||
|
import { SessionTabAvatar } from "@/pages/layout/session-tab-avatar"
|
||||||
import { getRelativeTime } from "@/utils/time"
|
import { getRelativeTime } from "@/utils/time"
|
||||||
import {
|
import {
|
||||||
|
createCommandPaletteCommandEntry,
|
||||||
createCommandPaletteFileEntry,
|
createCommandPaletteFileEntry,
|
||||||
createCommandPaletteModel,
|
createCommandPaletteModel,
|
||||||
|
createServerSessionEntries,
|
||||||
uniqueCommandPaletteEntries,
|
uniqueCommandPaletteEntries,
|
||||||
type CommandPaletteEntry,
|
type CommandPaletteEntry,
|
||||||
} from "./command-palette"
|
} from "./command-palette"
|
||||||
@@ -30,9 +37,6 @@ function matchesEntry(entry: CommandPaletteEntry, query: string) {
|
|||||||
|
|
||||||
export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => void }) {
|
export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => void }) {
|
||||||
const palette = createCommandPaletteModel(props)
|
const palette = createCommandPaletteModel(props)
|
||||||
const [query, setQuery] = createSignal("")
|
|
||||||
const [active, setActive] = createSignal(0)
|
|
||||||
|
|
||||||
const loadItems = async (text: string) => {
|
const loadItems = async (text: string) => {
|
||||||
const q = text.trim()
|
const q = text.trim()
|
||||||
if (!q) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()]
|
if (!q) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()]
|
||||||
@@ -41,16 +45,105 @@ export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => v
|
|||||||
const category = palette.language.t("palette.group.files")
|
const category = palette.language.t("palette.group.files")
|
||||||
return [
|
return [
|
||||||
...palette.commandEntries().filter((entry) => matchesEntry(entry, q)),
|
...palette.commandEntries().filter((entry) => matchesEntry(entry, q)),
|
||||||
...nextSessions.filter((entry) => matchesEntry(entry, q)),
|
...nextSessions,
|
||||||
...files.map((path) => createCommandPaletteFileEntry(path, category)),
|
...files.map((path) => createCommandPaletteFileEntry(path, category)),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const [entries] = createResource(query, loadItems, { initialValue: [] as CommandPaletteEntry[] })
|
return (
|
||||||
|
<CommandPaletteView
|
||||||
|
placeholder={palette.language.t("palette.search.placeholder")}
|
||||||
|
loadItems={loadItems}
|
||||||
|
highlight={palette.highlight}
|
||||||
|
select={palette.select}
|
||||||
|
close={palette.close}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogHomeCommandPaletteV2(props: {
|
||||||
|
server: ServerConnection.Any
|
||||||
|
onSelectSession: (entry: CommandPaletteEntry) => void
|
||||||
|
}) {
|
||||||
|
const command = useCommand()
|
||||||
|
const dialog = useDialog()
|
||||||
|
const global = useGlobal()
|
||||||
|
const language = useLanguage()
|
||||||
|
const serverCtx = global.ensureServerCtx(props.server)
|
||||||
|
const state = { cleanup: undefined as (() => void) | void, committed: false }
|
||||||
|
const commandEntries = createMemo(() => {
|
||||||
|
const category = language.t("palette.group.commands")
|
||||||
|
return commandPaletteOptions(command.options).map((option) => createCommandPaletteCommandEntry(option, category))
|
||||||
|
})
|
||||||
|
const sessions = createServerSessionEntries({
|
||||||
|
server: ServerConnection.key(props.server),
|
||||||
|
opened: serverCtx.projects.list,
|
||||||
|
stored: () => serverCtx.sync.data.project,
|
||||||
|
load: (search, signal) =>
|
||||||
|
serverCtx.sdk.client.experimental.session.list({ roots: true, search, limit: 50 }, { signal }),
|
||||||
|
untitled: () => language.t("command.session.new"),
|
||||||
|
category: () => language.t("command.category.session"),
|
||||||
|
})
|
||||||
|
|
||||||
|
const highlight = (item: CommandPaletteEntry | undefined) => {
|
||||||
|
state.cleanup?.()
|
||||||
|
state.cleanup = undefined
|
||||||
|
if (item?.type !== "command") return
|
||||||
|
state.cleanup = item.option?.onHighlight?.()
|
||||||
|
}
|
||||||
|
const select = (item: CommandPaletteEntry | undefined) => {
|
||||||
|
if (!item) return
|
||||||
|
state.committed = true
|
||||||
|
state.cleanup = undefined
|
||||||
|
dialog.close()
|
||||||
|
if (item.type === "command") {
|
||||||
|
item.option?.onSelect?.("palette")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (item.type === "session") props.onSelectSession(item)
|
||||||
|
}
|
||||||
|
const loadItems = async (text: string) => {
|
||||||
|
const query = text.trim()
|
||||||
|
if (!query) return commandEntries().slice(0, 5)
|
||||||
|
return [...commandEntries().filter((entry) => matchesEntry(entry, query)), ...(await sessions(query))]
|
||||||
|
}
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
if (state.committed) return
|
||||||
|
state.cleanup?.()
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommandPaletteView
|
||||||
|
placeholder={language.t("palette.search.placeholder.home")}
|
||||||
|
loadItems={loadItems}
|
||||||
|
highlight={highlight}
|
||||||
|
select={select}
|
||||||
|
close={() => dialog.close()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandPaletteView(props: {
|
||||||
|
placeholder: string
|
||||||
|
loadItems: (text: string) => CommandPaletteEntry[] | Promise<CommandPaletteEntry[]>
|
||||||
|
highlight: (item: CommandPaletteEntry | undefined) => void
|
||||||
|
select: (item: CommandPaletteEntry | undefined) => void
|
||||||
|
close: () => void
|
||||||
|
}) {
|
||||||
|
const language = useLanguage()
|
||||||
|
const tabs = useTabs()
|
||||||
|
const [query, setQuery] = createSignal("")
|
||||||
|
const [active, setActive] = createSignal(0)
|
||||||
|
|
||||||
|
const [entries] = createResource(query, props.loadItems, { initialValue: [] as CommandPaletteEntry[] })
|
||||||
// Render stale results while a new query loads to avoid flashing "Loading" per keystroke.
|
// Render stale results while a new query loads to avoid flashing "Loading" per keystroke.
|
||||||
const visibleEntries = createMemo(() => uniqueCommandPaletteEntries(entries.latest ?? []))
|
const visibleEntries = createMemo(() => uniqueCommandPaletteEntries(entries.latest ?? []))
|
||||||
const groupedEntries = createMemo(() => groups(visibleEntries()))
|
const groupedEntries = createMemo(() => groups(visibleEntries()))
|
||||||
const activeEntry = createMemo(() => visibleEntries()[active()])
|
const activeEntry = createMemo(() => visibleEntries()[active()])
|
||||||
|
const openSessions = createMemo(
|
||||||
|
() => new Set(tabs.store.flatMap((tab) => (tab.type === "session" ? [`${tab.server}\0${tab.sessionId}`] : []))),
|
||||||
|
)
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
query()
|
query()
|
||||||
@@ -59,7 +152,7 @@ export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => v
|
|||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
palette.highlight(activeEntry())
|
props.highlight(activeEntry())
|
||||||
})
|
})
|
||||||
|
|
||||||
let resultsRef: HTMLDivElement | undefined
|
let resultsRef: HTMLDivElement | undefined
|
||||||
@@ -86,12 +179,12 @@ export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => v
|
|||||||
}
|
}
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
palette.select(activeEntry())
|
props.select(activeEntry())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
palette.close()
|
props.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +198,7 @@ export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => v
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
spellcheck={false}
|
spellcheck={false}
|
||||||
appearance="large"
|
appearance="large"
|
||||||
placeholder={palette.language.t("palette.search.placeholder")}
|
placeholder={props.placeholder}
|
||||||
leadingIcon={<Icon name="magnifying-glass" />}
|
leadingIcon={<Icon name="magnifying-glass" />}
|
||||||
onInput={(event) => setQuery(event.currentTarget.value)}
|
onInput={(event) => setQuery(event.currentTarget.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
@@ -117,7 +210,7 @@ export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => v
|
|||||||
when={visibleEntries().length > 0}
|
when={visibleEntries().length > 0}
|
||||||
fallback={
|
fallback={
|
||||||
<div class="command-palette-v2-state">
|
<div class="command-palette-v2-state">
|
||||||
{entries.loading ? palette.language.t("common.loading") : palette.language.t("palette.empty")}
|
{entries.loading ? language.t("common.loading") : language.t("palette.empty")}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -132,9 +225,14 @@ export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => v
|
|||||||
<PaletteRow
|
<PaletteRow
|
||||||
item={item}
|
item={item}
|
||||||
active={activeEntry()?.id === item.id}
|
active={activeEntry()?.id === item.id}
|
||||||
language={palette.language}
|
language={language}
|
||||||
|
sessionOpen={
|
||||||
|
item.server && item.sessionID
|
||||||
|
? openSessions().has(`${item.server}\0${item.sessionID}`)
|
||||||
|
: false
|
||||||
|
}
|
||||||
onActive={() => setActive(visibleEntries().findIndex((entry) => entry.id === item.id))}
|
onActive={() => setActive(visibleEntries().findIndex((entry) => entry.id === item.id))}
|
||||||
onSelect={() => palette.select(item)}
|
onSelect={() => props.select(item)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
@@ -153,13 +251,19 @@ function PaletteRow(props: {
|
|||||||
item: CommandPaletteEntry
|
item: CommandPaletteEntry
|
||||||
active: boolean
|
active: boolean
|
||||||
language: ReturnType<typeof useLanguage>
|
language: ReturnType<typeof useLanguage>
|
||||||
|
sessionOpen: boolean
|
||||||
onActive: () => void
|
onActive: () => void
|
||||||
onSelect: () => void
|
onSelect: () => void
|
||||||
}) {
|
}) {
|
||||||
|
const session = () =>
|
||||||
|
props.item.server && props.item.directory && props.item.sessionID
|
||||||
|
? { server: props.item.server, directory: props.item.directory, sessionID: props.item.sessionID }
|
||||||
|
: undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="command-palette-v2-row"
|
class="command-palette-v2-row group"
|
||||||
role="option"
|
role="option"
|
||||||
aria-selected={props.active}
|
aria-selected={props.active}
|
||||||
data-active={props.active ? "" : undefined}
|
data-active={props.active ? "" : undefined}
|
||||||
@@ -197,7 +301,25 @@ function PaletteRow(props: {
|
|||||||
</Match>
|
</Match>
|
||||||
<Match when={props.item.type === "session"}>
|
<Match when={props.item.type === "session"}>
|
||||||
<div class="command-palette-v2-row-main">
|
<div class="command-palette-v2-row-main">
|
||||||
<Icon name="status" class="command-palette-v2-row-icon" />
|
<div class="relative shrink-0">
|
||||||
|
<Show when={props.sessionOpen}>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="pointer-events-none absolute top-1/2 h-3 w-0.5 -translate-y-1/2 rounded-[2px] bg-v2-background-bg-layer-04"
|
||||||
|
style={{ right: "calc(100% + 4px)" }}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
<Show when={session()}>
|
||||||
|
{(session) => (
|
||||||
|
<SessionTabAvatar
|
||||||
|
project={props.item.project}
|
||||||
|
directory={session().directory}
|
||||||
|
sessionId={session().sessionID}
|
||||||
|
server={session().server}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
<div class="command-palette-v2-row-text">
|
<div class="command-palette-v2-row-text">
|
||||||
<span class="command-palette-v2-title" classList={{ "opacity-70": !!props.item.archived }}>
|
<span class="command-palette-v2-title" classList={{ "opacity-70": !!props.item.archived }}>
|
||||||
{props.item.title}
|
{props.item.title}
|
||||||
|
|||||||
@@ -8,61 +8,20 @@ import introducingTabsVideo from "@/assets/help/introducing-tabs.mp4"
|
|||||||
import homeImage from "@/assets/help/home.png"
|
import homeImage from "@/assets/help/home.png"
|
||||||
import tabsImage from "@/assets/help/tabs.png"
|
import tabsImage from "@/assets/help/tabs.png"
|
||||||
|
|
||||||
const helpIcon = (
|
|
||||||
<svg
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon-svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M6.94235 10.5714V10.4854C6.94617 9.76302 7.01879 9.18777 7.16022 8.75968C7.30546 8.33158 7.50804 7.98567 7.76796 7.72193C8.02787 7.45819 8.34321 7.21548 8.71397 6.99379C8.93948 6.85619 9.14206 6.69374 9.32171 6.50645C9.50518 6.31916 9.64851 6.10511 9.75171 5.86431C9.85874 5.62351 9.91225 5.35404 9.91225 5.0559C9.91225 4.69661 9.82625 4.38509 9.65424 4.12136C9.48607 3.85762 9.26055 3.65504 8.9777 3.51362C8.69486 3.36837 8.38143 3.29575 8.03743 3.29575C7.73165 3.29575 7.43733 3.35882 7.15448 3.48495C6.87546 3.61108 6.6423 3.80984 6.45501 4.08122C6.26772 4.3526 6.15878 4.70425 6.12821 5.13617H4.56299C4.59357 4.47109 4.76557 3.9054 5.07899 3.43908C5.39242 2.96894 5.80522 2.61156 6.31741 2.36694C6.83341 2.12231 7.40675 2 8.03743 2C8.72161 2 9.31789 2.13378 9.82625 2.40134C10.3384 2.66507 10.734 3.0301 11.0131 3.49642C11.2959 3.96273 11.4373 4.49976 11.4373 5.1075C11.4373 5.53177 11.3724 5.914 11.2424 6.25418C11.1124 6.59436 10.9251 6.89823 10.6805 7.16579C10.4397 7.43335 10.1492 7.67033 9.80905 7.87673C9.48033 8.08313 9.21468 8.301 9.0121 8.53034C8.80952 8.75585 8.66237 9.02341 8.57063 9.33302C8.4789 9.64262 8.42921 10.0268 8.42156 10.4854V10.5714H6.94235ZM7.72782 14C7.43351 14 7.17933 13.8949 6.96528 13.6847C6.75506 13.4744 6.64994 13.2203 6.64994 12.9221C6.64994 12.6278 6.75506 12.3755 6.96528 12.1653C7.17933 11.9551 7.43351 11.85 7.72782 11.85C8.02214 11.85 8.27441 11.9551 8.48463 12.1653C8.69868 12.3755 8.8057 12.6278 8.8057 12.9221C8.8057 13.1209 8.75601 13.3024 8.65663 13.4668C8.55726 13.6273 8.4273 13.7573 8.26676 13.8567C8.10623 13.9522 7.92658 14 7.72782 14Z"
|
|
||||||
fill="var(--v2-icon-icon-base)"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
const triggerClass =
|
|
||||||
"size-7 !rounded-full shrink-0 bg-v2-background-bg-base shadow-[var(--v2-elevation-button-neutral)]"
|
|
||||||
|
|
||||||
// TODO: wire to changelog / seen-state when available
|
// TODO: wire to changelog / seen-state when available
|
||||||
const showPopover = () => true
|
const showPopover = () => true
|
||||||
|
|
||||||
export function HelpButton() {
|
|
||||||
if (import.meta.env.VITE_OPENCODE_CHANNEL !== "dev") return null
|
|
||||||
|
|
||||||
const platform = usePlatform()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
href="https://opencode.ai"
|
|
||||||
aria-label="Open the OpenCode website"
|
|
||||||
data-component="icon-button-v2"
|
|
||||||
data-size="large"
|
|
||||||
class={`${triggerClass} fixed bottom-5 right-5 z-50 flex items-center justify-center`}
|
|
||||||
onClick={(event) => {
|
|
||||||
event.preventDefault()
|
|
||||||
platform.openLink(event.currentTarget.href)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{helpIcon}
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// can remove this after the tabs rollout has been out for a while
|
// can remove this after the tabs rollout has been out for a while
|
||||||
export function TabsInfoPopup() {
|
export function TabsInfoPopup() {
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
|
const platform = usePlatform()
|
||||||
const [drawerOpen, setDrawerOpen] = createSignal(false)
|
const [drawerOpen, setDrawerOpen] = createSignal(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer open={drawerOpen()} onOpenChange={setDrawerOpen} side="right">
|
<Drawer open={drawerOpen()} onOpenChange={setDrawerOpen} side="right">
|
||||||
<Show when={settings.general.shouldDisplayTabsToast()}>
|
<Show when={settings.general.shouldDisplayTabsToast()}>
|
||||||
<div
|
<div
|
||||||
class="fixed bottom-14 right-5 z-50 h-[240px] w-[192px] rounded-[8px] bg-v2-background-bg-base p-1 shadow-[var(--v2-elevation-floating)]"
|
class="fixed bottom-5 right-5 z-50 h-[240px] w-[192px] rounded-[8px] bg-v2-background-bg-base p-1 shadow-[var(--v2-elevation-floating)]"
|
||||||
aria-label="Introducing Tabs. Organize your work and active sessions with tabs"
|
aria-label="Introducing Tabs. Organize your work and active sessions with tabs"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -114,16 +73,18 @@ export function TabsInfoPopup() {
|
|||||||
<DrawerContent>
|
<DrawerContent>
|
||||||
<div class="flex h-[52px] w-full shrink-0 items-center gap-4 self-stretch border-b border-v2-border-border-muted p-4">
|
<div class="flex h-[52px] w-full shrink-0 items-center gap-4 self-stretch border-b border-v2-border-border-muted p-4">
|
||||||
<p class="min-h-0 min-w-0 flex-1 text-[13px] font-[530] leading-5 tracking-[-0.04px] tabular-nums text-v2-text-text-muted">
|
<p class="min-h-0 min-w-0 flex-1 text-[13px] font-[530] leading-5 tracking-[-0.04px] tabular-nums text-v2-text-text-muted">
|
||||||
June 16
|
July 14
|
||||||
</p>
|
</p>
|
||||||
<DrawerClose
|
<Show when={platform.platform !== "desktop" || platform.os !== "windows"}>
|
||||||
as={IconButtonV2}
|
<DrawerClose
|
||||||
type="button"
|
as={IconButtonV2}
|
||||||
size="small"
|
type="button"
|
||||||
variant="ghost-muted"
|
size="small"
|
||||||
aria-label="Close"
|
variant="ghost-muted"
|
||||||
icon={<IconV2 name="xmark-small" />}
|
aria-label="Close"
|
||||||
/>
|
icon={<IconV2 name="xmark-small" />}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative flex min-h-0 w-full flex-1 flex-col items-start gap-6 overflow-y-auto p-8">
|
<div class="relative flex min-h-0 w-full flex-1 flex-col items-start gap-6 overflow-y-auto p-8">
|
||||||
<p class="w-full shrink-0 self-stretch text-[21px] font-[610] leading-6 tracking-[-0.37px] tabular-nums text-v2-text-text-base">
|
<p class="w-full shrink-0 self-stretch text-[21px] font-[610] leading-6 tracking-[-0.37px] tabular-nums text-v2-text-text-base">
|
||||||
@@ -147,7 +108,7 @@ export function TabsInfoPopup() {
|
|||||||
<p>When you reopen the app, your tabs are still open.</p>
|
<p>When you reopen the app, your tabs are still open.</p>
|
||||||
<p>
|
<p>
|
||||||
The new design does not support Git Worktrees yet, it's coming soon. So if you'd prefer to continue using
|
The new design does not support Git Worktrees yet, it's coming soon. So if you'd prefer to continue using
|
||||||
the previous layout , you can switch between layouts in Settings. Just keep in mind that the new layout
|
the previous layout, you can switch between layouts in Settings. Just keep in mind that the new layout
|
||||||
will become permanent in a few weeks.
|
will become permanent in a few weeks.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
|||||||
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import fuzzysort from "fuzzysort"
|
import fuzzysort from "fuzzysort"
|
||||||
import { formatKeybind, parseKeybind, useCommand } from "@/context/command"
|
import { DEFAULT_PALETTE_KEYBIND, formatKeybind, parseKeybind, useCommand } from "@/context/command"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useSettings } from "@/context/settings"
|
import { useSettings } from "@/context/settings"
|
||||||
import { SettingsList } from "./settings-list"
|
import { SettingsList } from "./settings-list"
|
||||||
@@ -20,7 +20,6 @@ const IconV2 = lazy(() => import("@opencode-ai/ui/v2/icon").then((module) => ({
|
|||||||
|
|
||||||
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
|
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
|
||||||
const PALETTE_ID = "command.palette"
|
const PALETTE_ID = "command.palette"
|
||||||
const DEFAULT_PALETTE_KEYBIND = "mod+shift+p"
|
|
||||||
|
|
||||||
type KeybindGroup = "General" | "Session" | "Navigation" | "Model and agent" | "Terminal" | "Prompt"
|
type KeybindGroup = "General" | "Session" | "Navigation" | "Model and agent" | "Terminal" | "Prompt"
|
||||||
|
|
||||||
|
|||||||
@@ -371,7 +371,7 @@
|
|||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-v2-tab-body.settings-v2-models {
|
.settings-v2-models {
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
id: "tab.new",
|
id: "tab.new",
|
||||||
category: "tab",
|
category: "tab",
|
||||||
title: language.t("command.session.new"),
|
title: language.t("command.session.new"),
|
||||||
keybind: "mod+t",
|
keybind: "mod+t,mod+n",
|
||||||
hidden: true,
|
hidden: true,
|
||||||
onSelect: openNewTab,
|
onSelect: openNewTab,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import { resolveKeybindOption, upsertCommandRegistration } from "./command"
|
import { commandPaletteOptions, resolveKeybindOption, upsertCommandRegistration, type CommandOption } from "./command"
|
||||||
|
|
||||||
|
const paletteOptions: CommandOption[] = [
|
||||||
|
{ id: "settings.open", title: "Open settings" },
|
||||||
|
{ id: "session.undo", title: "Undo" },
|
||||||
|
{ id: "file.open", title: "Open file" },
|
||||||
|
{ id: "hidden", title: "Hidden", hidden: true },
|
||||||
|
{ id: "disabled", title: "Disabled", disabled: true },
|
||||||
|
]
|
||||||
|
|
||||||
|
describe("commandPaletteOptions", () => {
|
||||||
|
test("keeps visible enabled commands", () => {
|
||||||
|
expect(commandPaletteOptions(paletteOptions).map((option) => option.id)).toEqual(["settings.open", "session.undo"])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("upsertCommandRegistration", () => {
|
describe("upsertCommandRegistration", () => {
|
||||||
test("replaces keyed registrations", () => {
|
test("replaces keyed registrations", () => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { Persist, persisted } from "@/utils/persist"
|
|||||||
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
|
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
|
||||||
|
|
||||||
const PALETTE_ID = "command.palette"
|
const PALETTE_ID = "command.palette"
|
||||||
const DEFAULT_PALETTE_KEYBIND = "mod+shift+p"
|
export const DEFAULT_PALETTE_KEYBIND = "mod+k,mod+shift+p"
|
||||||
const SUGGESTED_PREFIX = "suggested."
|
const SUGGESTED_PREFIX = "suggested."
|
||||||
const EDITABLE_KEYBIND_IDS = new Set(["terminal.toggle", "terminal.new", "file.attach"])
|
const EDITABLE_KEYBIND_IDS = new Set(["terminal.toggle", "terminal.new", "file.attach"])
|
||||||
|
|
||||||
@@ -87,6 +87,13 @@ export interface CommandOption {
|
|||||||
onHighlight?: () => (() => void) | void
|
onHighlight?: () => (() => void) | void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function commandPaletteOptions(options: CommandOption[]) {
|
||||||
|
return options.filter(
|
||||||
|
(option) =>
|
||||||
|
!option.disabled && !option.hidden && !option.id.startsWith(SUGGESTED_PREFIX) && option.id !== "file.open",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveKeybindOption(candidates: CommandOption[] | undefined, event: KeyboardEvent) {
|
export function resolveKeybindOption(candidates: CommandOption[] | undefined, event: KeyboardEvent) {
|
||||||
return candidates?.find((option) => option.when?.(event)) ?? candidates?.find((option) => !option.when)
|
return candidates?.find((option) => option.when?.(event)) ?? candidates?.find((option) => !option.when)
|
||||||
}
|
}
|
||||||
@@ -375,7 +382,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||||||
}
|
}
|
||||||
|
|
||||||
const showPalette = () => {
|
const showPalette = () => {
|
||||||
run("file.open", "palette")
|
run(PALETTE_ID, "palette")
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import { hasCustomAgent, resolveAgent } from "./local-agent"
|
||||||
|
|
||||||
|
describe("hasCustomAgent", () => {
|
||||||
|
test("detects explicitly custom agents", () => {
|
||||||
|
expect(hasCustomAgent([{ native: true }, { native: false }])).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("ignores built-in and unclassified agents", () => {
|
||||||
|
expect(hasCustomAgent([{ native: true }, {}])).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("resolveAgent", () => {
|
||||||
|
const agents = [{ name: "plan" }, { name: "build" }, { name: "custom" }]
|
||||||
|
|
||||||
|
test("uses the requested available agent", () => {
|
||||||
|
expect(resolveAgent(agents, "custom")?.name).toBe("custom")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("defaults to build", () => {
|
||||||
|
expect(resolveAgent(agents)?.name).toBe("build")
|
||||||
|
expect(resolveAgent(agents, "missing")?.name).toBe("build")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("uses the first agent when build is unavailable", () => {
|
||||||
|
expect(resolveAgent([{ name: "custom" }], "missing")?.name).toBe("custom")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export function hasCustomAgent(items: Array<{ native?: boolean }>) {
|
||||||
|
return items.some((item) => item.native === false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveAgent<T extends { name: string }>(items: T[], name?: string) {
|
||||||
|
return items.find((item) => item.name === name) ?? items.find((item) => item.name === "build") ?? items[0]
|
||||||
|
}
|
||||||
@@ -4,8 +4,10 @@ import { useParams } from "@solidjs/router"
|
|||||||
import { batch, createEffect, createMemo, startTransition } from "solid-js"
|
import { batch, createEffect, createMemo, startTransition } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useModels } from "@/context/models"
|
import { useModels } from "@/context/models"
|
||||||
|
import { useSettings } from "@/context/settings"
|
||||||
import { useProviders } from "@/hooks/use-providers"
|
import { useProviders } from "@/hooks/use-providers"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
|
import { hasCustomAgent, resolveAgent } from "./local-agent"
|
||||||
import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant"
|
import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant"
|
||||||
import { useSDK } from "./sdk"
|
import { useSDK } from "./sdk"
|
||||||
import { useSync } from "./sync"
|
import { useSync } from "./sync"
|
||||||
@@ -62,9 +64,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
const serverSDK = useServerSDK()
|
const serverSDK = useServerSDK()
|
||||||
const providers = useProviders(() => sdk().directory)
|
const providers = useProviders(() => sdk().directory)
|
||||||
const models = useModels()
|
const models = useModels()
|
||||||
|
const settings = useSettings()
|
||||||
|
|
||||||
const id = createMemo(() => params.id || undefined)
|
const id = createMemo(() => params.id || undefined)
|
||||||
const list = createMemo(() => sync().data.agent.filter((item) => item.mode !== "subagent" && !item.hidden))
|
const list = createMemo(() => sync().data.agent.filter((item) => item.mode !== "subagent" && !item.hidden))
|
||||||
|
const agentsVisible = createMemo(() => settings.visibility.customAgents() || hasCustomAgent(list()))
|
||||||
const connected = createMemo(() => new Set(providers.connected().map((item) => item.id)))
|
const connected = createMemo(() => new Set(providers.connected().map((item) => item.id)))
|
||||||
|
|
||||||
const [saved, setSaved, , savedReady] = persisted(
|
const [saved, setSaved, , savedReady] = persisted(
|
||||||
@@ -107,9 +111,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pickAgent = (name: string | undefined) => {
|
const pickAgent = (name: string | undefined) => {
|
||||||
const items = list()
|
return resolveAgent(list(), name)
|
||||||
if (items.length === 0) return
|
|
||||||
return items.find((item) => item.name === name) ?? items[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
@@ -180,8 +182,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
|
|
||||||
const agent = {
|
const agent = {
|
||||||
list,
|
list,
|
||||||
|
visible: agentsVisible,
|
||||||
current() {
|
current() {
|
||||||
return pickAgent(scope()?.agent ?? store.current)
|
return pickAgent(agentsVisible() ? (scope()?.agent ?? store.current) : "build")
|
||||||
},
|
},
|
||||||
set(name: string | undefined) {
|
set(name: string | undefined) {
|
||||||
const item = pickAgent(name)
|
const item = pickAgent(name)
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export const dict = {
|
|||||||
"command.session.unshare": "إلغاء مشاركة الجلسة",
|
"command.session.unshare": "إلغاء مشاركة الجلسة",
|
||||||
"command.session.unshare.description": "إيقاف مشاركة هذه الجلسة",
|
"command.session.unshare.description": "إيقاف مشاركة هذه الجلسة",
|
||||||
"palette.search.placeholder": "البحث في الملفات والأوامر والجلسات",
|
"palette.search.placeholder": "البحث في الملفات والأوامر والجلسات",
|
||||||
|
"palette.search.placeholder.home": "البحث في الأوامر والجلسات",
|
||||||
"palette.empty": "لا توجد نتائج",
|
"palette.empty": "لا توجد نتائج",
|
||||||
"palette.group.commands": "الأوامر",
|
"palette.group.commands": "الأوامر",
|
||||||
"palette.group.files": "الملفات",
|
"palette.group.files": "الملفات",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export const dict = {
|
|||||||
"command.session.unshare": "Parar de compartilhar sessão",
|
"command.session.unshare": "Parar de compartilhar sessão",
|
||||||
"command.session.unshare.description": "Parar de compartilhar esta sessão",
|
"command.session.unshare.description": "Parar de compartilhar esta sessão",
|
||||||
"palette.search.placeholder": "Buscar arquivos, comandos e sessões",
|
"palette.search.placeholder": "Buscar arquivos, comandos e sessões",
|
||||||
|
"palette.search.placeholder.home": "Buscar comandos e sessões",
|
||||||
"palette.empty": "Nenhum resultado encontrado",
|
"palette.empty": "Nenhum resultado encontrado",
|
||||||
"palette.group.commands": "Comandos",
|
"palette.group.commands": "Comandos",
|
||||||
"palette.group.files": "Arquivos",
|
"palette.group.files": "Arquivos",
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export const dict = {
|
|||||||
"command.session.unshare.description": "Zaustavi dijeljenje ove sesije",
|
"command.session.unshare.description": "Zaustavi dijeljenje ove sesije",
|
||||||
|
|
||||||
"palette.search.placeholder": "Pretraži datoteke, komande i sesije",
|
"palette.search.placeholder": "Pretraži datoteke, komande i sesije",
|
||||||
|
"palette.search.placeholder.home": "Pretraži komande i sesije",
|
||||||
"palette.empty": "Nema rezultata",
|
"palette.empty": "Nema rezultata",
|
||||||
"palette.group.commands": "Komande",
|
"palette.group.commands": "Komande",
|
||||||
"palette.group.files": "Datoteke",
|
"palette.group.files": "Datoteke",
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export const dict = {
|
|||||||
"command.session.unshare.description": "Stop med at dele denne session",
|
"command.session.unshare.description": "Stop med at dele denne session",
|
||||||
|
|
||||||
"palette.search.placeholder": "Søg i filer, kommandoer og sessioner",
|
"palette.search.placeholder": "Søg i filer, kommandoer og sessioner",
|
||||||
|
"palette.search.placeholder.home": "Søg i kommandoer og sessioner",
|
||||||
"palette.empty": "Ingen resultater fundet",
|
"palette.empty": "Ingen resultater fundet",
|
||||||
"palette.group.commands": "Kommandoer",
|
"palette.group.commands": "Kommandoer",
|
||||||
"palette.group.files": "Filer",
|
"palette.group.files": "Filer",
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export const dict = {
|
|||||||
"command.session.unshare": "Teilen der Sitzung aufheben",
|
"command.session.unshare": "Teilen der Sitzung aufheben",
|
||||||
"command.session.unshare.description": "Teilen dieser Sitzung beenden",
|
"command.session.unshare.description": "Teilen dieser Sitzung beenden",
|
||||||
"palette.search.placeholder": "Dateien, Befehle und Sitzungen durchsuchen",
|
"palette.search.placeholder": "Dateien, Befehle und Sitzungen durchsuchen",
|
||||||
|
"palette.search.placeholder.home": "Befehle und Sitzungen durchsuchen",
|
||||||
"palette.empty": "Keine Ergebnisse gefunden",
|
"palette.empty": "Keine Ergebnisse gefunden",
|
||||||
"palette.group.commands": "Befehle",
|
"palette.group.commands": "Befehle",
|
||||||
"palette.group.files": "Dateien",
|
"palette.group.files": "Dateien",
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export const dict = {
|
|||||||
"command.session.unshare.description": "Stop sharing this session",
|
"command.session.unshare.description": "Stop sharing this session",
|
||||||
|
|
||||||
"palette.search.placeholder": "Search files, commands, and sessions",
|
"palette.search.placeholder": "Search files, commands, and sessions",
|
||||||
|
"palette.search.placeholder.home": "Search commands and sessions",
|
||||||
"palette.empty": "No results found",
|
"palette.empty": "No results found",
|
||||||
"palette.group.commands": "Commands",
|
"palette.group.commands": "Commands",
|
||||||
"palette.group.files": "Files",
|
"palette.group.files": "Files",
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export const dict = {
|
|||||||
"command.session.unshare.description": "Dejar de compartir esta sesión",
|
"command.session.unshare.description": "Dejar de compartir esta sesión",
|
||||||
|
|
||||||
"palette.search.placeholder": "Buscar archivos, comandos y sesiones",
|
"palette.search.placeholder": "Buscar archivos, comandos y sesiones",
|
||||||
|
"palette.search.placeholder.home": "Buscar comandos y sesiones",
|
||||||
"palette.empty": "No se encontraron resultados",
|
"palette.empty": "No se encontraron resultados",
|
||||||
"palette.group.commands": "Comandos",
|
"palette.group.commands": "Comandos",
|
||||||
"palette.group.files": "Archivos",
|
"palette.group.files": "Archivos",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export const dict = {
|
|||||||
"command.session.unshare": "Ne plus partager la session",
|
"command.session.unshare": "Ne plus partager la session",
|
||||||
"command.session.unshare.description": "Arrêter de partager cette session",
|
"command.session.unshare.description": "Arrêter de partager cette session",
|
||||||
"palette.search.placeholder": "Rechercher des fichiers, des commandes et des sessions",
|
"palette.search.placeholder": "Rechercher des fichiers, des commandes et des sessions",
|
||||||
|
"palette.search.placeholder.home": "Rechercher des commandes et des sessions",
|
||||||
"palette.empty": "Aucun résultat trouvé",
|
"palette.empty": "Aucun résultat trouvé",
|
||||||
"palette.group.commands": "Commandes",
|
"palette.group.commands": "Commandes",
|
||||||
"palette.group.files": "Fichiers",
|
"palette.group.files": "Fichiers",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export const dict = {
|
|||||||
"command.session.unshare": "セッションの共有を停止",
|
"command.session.unshare": "セッションの共有を停止",
|
||||||
"command.session.unshare.description": "このセッションの共有を停止",
|
"command.session.unshare.description": "このセッションの共有を停止",
|
||||||
"palette.search.placeholder": "ファイル、コマンド、セッションを検索",
|
"palette.search.placeholder": "ファイル、コマンド、セッションを検索",
|
||||||
|
"palette.search.placeholder.home": "コマンドとセッションを検索",
|
||||||
"palette.empty": "結果が見つかりません",
|
"palette.empty": "結果が見つかりません",
|
||||||
"palette.group.commands": "コマンド",
|
"palette.group.commands": "コマンド",
|
||||||
"palette.group.files": "ファイル",
|
"palette.group.files": "ファイル",
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export const dict = {
|
|||||||
"command.session.unshare": "세션 공유 중지",
|
"command.session.unshare": "세션 공유 중지",
|
||||||
"command.session.unshare.description": "이 세션 공유 중지",
|
"command.session.unshare.description": "이 세션 공유 중지",
|
||||||
"palette.search.placeholder": "파일, 명령어 및 세션 검색",
|
"palette.search.placeholder": "파일, 명령어 및 세션 검색",
|
||||||
|
"palette.search.placeholder.home": "명령어 및 세션 검색",
|
||||||
"palette.empty": "결과 없음",
|
"palette.empty": "결과 없음",
|
||||||
"palette.group.commands": "명령어",
|
"palette.group.commands": "명령어",
|
||||||
"palette.group.files": "파일",
|
"palette.group.files": "파일",
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ export const dict = {
|
|||||||
"command.session.unshare.description": "Slutt å dele denne sesjonen",
|
"command.session.unshare.description": "Slutt å dele denne sesjonen",
|
||||||
|
|
||||||
"palette.search.placeholder": "Søk i filer, kommandoer og sesjoner",
|
"palette.search.placeholder": "Søk i filer, kommandoer og sesjoner",
|
||||||
|
"palette.search.placeholder.home": "Søk i kommandoer og sesjoner",
|
||||||
"palette.empty": "Ingen resultater funnet",
|
"palette.empty": "Ingen resultater funnet",
|
||||||
"palette.group.commands": "Kommandoer",
|
"palette.group.commands": "Kommandoer",
|
||||||
"palette.group.files": "Filer",
|
"palette.group.files": "Filer",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export const dict = {
|
|||||||
"command.session.unshare": "Przestań udostępniać sesję",
|
"command.session.unshare": "Przestań udostępniać sesję",
|
||||||
"command.session.unshare.description": "Zatrzymaj udostępnianie tej sesji",
|
"command.session.unshare.description": "Zatrzymaj udostępnianie tej sesji",
|
||||||
"palette.search.placeholder": "Szukaj plików, poleceń i sesji",
|
"palette.search.placeholder": "Szukaj plików, poleceń i sesji",
|
||||||
|
"palette.search.placeholder.home": "Szukaj poleceń i sesji",
|
||||||
"palette.empty": "Brak wyników",
|
"palette.empty": "Brak wyników",
|
||||||
"palette.group.commands": "Polecenia",
|
"palette.group.commands": "Polecenia",
|
||||||
"palette.group.files": "Pliki",
|
"palette.group.files": "Pliki",
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export const dict = {
|
|||||||
"command.session.unshare.description": "Прекратить публикацию сессии",
|
"command.session.unshare.description": "Прекратить публикацию сессии",
|
||||||
|
|
||||||
"palette.search.placeholder": "Поиск файлов, команд и сессий",
|
"palette.search.placeholder": "Поиск файлов, команд и сессий",
|
||||||
|
"palette.search.placeholder.home": "Поиск команд и сессий",
|
||||||
"palette.empty": "Ничего не найдено",
|
"palette.empty": "Ничего не найдено",
|
||||||
"palette.group.commands": "Команды",
|
"palette.group.commands": "Команды",
|
||||||
"palette.group.files": "Файлы",
|
"palette.group.files": "Файлы",
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export const dict = {
|
|||||||
"command.session.unshare.description": "หยุดการแชร์เซสชันนี้",
|
"command.session.unshare.description": "หยุดการแชร์เซสชันนี้",
|
||||||
|
|
||||||
"palette.search.placeholder": "ค้นหาไฟล์ คำสั่ง และเซสชัน",
|
"palette.search.placeholder": "ค้นหาไฟล์ คำสั่ง และเซสชัน",
|
||||||
|
"palette.search.placeholder.home": "ค้นหาคำสั่งและเซสชัน",
|
||||||
"palette.empty": "ไม่พบผลลัพธ์",
|
"palette.empty": "ไม่พบผลลัพธ์",
|
||||||
"palette.group.commands": "คำสั่ง",
|
"palette.group.commands": "คำสั่ง",
|
||||||
"palette.group.files": "ไฟล์",
|
"palette.group.files": "ไฟล์",
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ export const dict = {
|
|||||||
"command.session.unshare.description": "Bu oturumun paylaşımını durdur",
|
"command.session.unshare.description": "Bu oturumun paylaşımını durdur",
|
||||||
|
|
||||||
"palette.search.placeholder": "Dosya, komut ve oturum ara",
|
"palette.search.placeholder": "Dosya, komut ve oturum ara",
|
||||||
|
"palette.search.placeholder.home": "Komut ve oturum ara",
|
||||||
"palette.empty": "Sonuç bulunamadı",
|
"palette.empty": "Sonuç bulunamadı",
|
||||||
"palette.group.commands": "Komutlar",
|
"palette.group.commands": "Komutlar",
|
||||||
"palette.group.files": "Dosyalar",
|
"palette.group.files": "Dosyalar",
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export const dict = {
|
|||||||
"command.session.unshare.description": "Припинити поширення цієї сесії",
|
"command.session.unshare.description": "Припинити поширення цієї сесії",
|
||||||
|
|
||||||
"palette.search.placeholder": "Пошук файлів, команд і сесій",
|
"palette.search.placeholder": "Пошук файлів, команд і сесій",
|
||||||
|
"palette.search.placeholder.home": "Пошук команд і сесій",
|
||||||
"palette.empty": "Результатів не знайдено",
|
"palette.empty": "Результатів не знайдено",
|
||||||
"palette.group.commands": "Команди",
|
"palette.group.commands": "Команди",
|
||||||
"palette.group.files": "Файли",
|
"palette.group.files": "Файли",
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export const dict = {
|
|||||||
"command.session.unshare.description": "停止分享此会话",
|
"command.session.unshare.description": "停止分享此会话",
|
||||||
|
|
||||||
"palette.search.placeholder": "搜索文件、命令和会话",
|
"palette.search.placeholder": "搜索文件、命令和会话",
|
||||||
|
"palette.search.placeholder.home": "搜索命令和会话",
|
||||||
"palette.empty": "未找到结果",
|
"palette.empty": "未找到结果",
|
||||||
"palette.group.commands": "命令",
|
"palette.group.commands": "命令",
|
||||||
"palette.group.files": "文件",
|
"palette.group.files": "文件",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user