mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-13 23:09:50 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a8da2c985 |
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"@opencode-ai/core": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Preserve prompt cache prefixes when sessions move between locations with unchanged instructions.
|
|
||||||
@@ -1,4 +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/models-dev/snapshot.txt linguist-generated
|
|
||||||
packages/core/src/**/*.txt text eol=lf
|
packages/core/src/**/*.txt text eol=lf
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ kitlangton
|
|||||||
kommander
|
kommander
|
||||||
ludvigrask
|
ludvigrask
|
||||||
MrMushrooooom
|
MrMushrooooom
|
||||||
neriousy
|
|
||||||
nexxeln
|
nexxeln
|
||||||
R44VC0RP
|
R44VC0RP
|
||||||
rekram1-node
|
rekram1-node
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
name: deploy-lab-catalog
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [v2]
|
|
||||||
paths:
|
|
||||||
- ".github/workflows/deploy-lab-catalog.yml"
|
|
||||||
- "bun.lock"
|
|
||||||
- "package.json"
|
|
||||||
- "packages/drive/**"
|
|
||||||
- "packages/protocol/src/simulation.ts"
|
|
||||||
- "packages/simulation/**"
|
|
||||||
- "packages/lab/catalog/**"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: deploy-lab-catalog-${{ github.ref_name }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
if: github.repository == 'anomalyco/opencode' && github.ref_name == 'v2'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-bun
|
|
||||||
|
|
||||||
- name: Install ffmpeg
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install --yes ffmpeg
|
|
||||||
|
|
||||||
- name: Validate
|
|
||||||
run: |
|
|
||||||
bun --cwd packages/protocol typecheck
|
|
||||||
bun --cwd packages/simulation typecheck
|
|
||||||
bun --cwd packages/drive run check
|
|
||||||
bun --cwd packages/drive run test
|
|
||||||
bun --cwd packages/lab/catalog run check
|
|
||||||
|
|
||||||
- name: Deploy
|
|
||||||
working-directory: packages/lab/catalog
|
|
||||||
run: bun run deploy
|
|
||||||
env:
|
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
@@ -4,7 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
- v2
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
generate:
|
generate:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- ci
|
- ci
|
||||||
- dev
|
- dev
|
||||||
|
- v2
|
||||||
- beta
|
- beta
|
||||||
- fix/npm-native-binary-install
|
- fix/npm-native-binary-install
|
||||||
- snapshot-*
|
- snapshot-*
|
||||||
@@ -74,7 +75,7 @@ jobs:
|
|||||||
build-cli:
|
build-cli:
|
||||||
needs: version
|
needs: version
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'beta'
|
if: github.repository == 'anomalyco/opencode'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
with:
|
with:
|
||||||
@@ -90,7 +91,7 @@ jobs:
|
|||||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||||
|
|
||||||
- name: Build legacy CLI
|
- name: Build legacy CLI
|
||||||
if: github.ref_name != 'v2' && github.ref_name != 'beta'
|
if: github.ref_name != 'v2'
|
||||||
run: ./packages/opencode/script/build.ts ${{ (github.ref_name == 'beta' && '--sourcemaps') || '' }}
|
run: ./packages/opencode/script/build.ts ${{ (github.ref_name == 'beta' && '--sourcemaps') || '' }}
|
||||||
env:
|
env:
|
||||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||||
@@ -108,7 +109,7 @@ jobs:
|
|||||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||||
|
|
||||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
if: github.ref_name != 'v2' && github.ref_name != 'beta'
|
if: github.ref_name != 'v2'
|
||||||
with:
|
with:
|
||||||
name: opencode-cli
|
name: opencode-cli
|
||||||
path: |
|
path: |
|
||||||
@@ -116,76 +117,22 @@ jobs:
|
|||||||
packages/opencode/dist/opencode-linux*
|
packages/opencode/dist/opencode-linux*
|
||||||
|
|
||||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
if: github.ref_name != 'v2' && github.ref_name != 'beta'
|
if: github.ref_name != 'v2'
|
||||||
with:
|
with:
|
||||||
name: opencode-cli-windows
|
name: opencode-cli-windows
|
||||||
path: packages/opencode/dist/opencode-windows*
|
path: packages/opencode/dist/opencode-windows*
|
||||||
|
|
||||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: opencode-preview-cli-unsigned
|
name: opencode-preview-cli
|
||||||
path: packages/cli/dist/cli-*
|
path: packages/cli/dist/cli-*
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ needs.version.outputs.version }}
|
version: ${{ needs.version.outputs.version }}
|
||||||
|
|
||||||
sign-cli-macos:
|
|
||||||
needs: build-cli
|
|
||||||
runs-on: macos-26
|
|
||||||
if: github.repository == 'anomalyco/opencode'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
|
||||||
|
|
||||||
- uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0
|
|
||||||
with:
|
|
||||||
keychain: build
|
|
||||||
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
|
|
||||||
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
||||||
with:
|
|
||||||
name: opencode-preview-cli-unsigned
|
|
||||||
path: packages/cli/dist
|
|
||||||
|
|
||||||
- name: Sign macOS CLI binaries
|
|
||||||
run: |
|
|
||||||
identity=$(security find-identity -v -p codesigning build.keychain | sed -n 's/.*"\(Developer ID Application:.*\)"/\1/p' | head -n 1)
|
|
||||||
if [ -z "$identity" ]; then
|
|
||||||
echo "Developer ID Application identity not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
found=0
|
|
||||||
for file in packages/cli/dist/cli-darwin-*/bin/opencode2; do
|
|
||||||
if [ ! -f "$file" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
found=1
|
|
||||||
codesign \
|
|
||||||
--force \
|
|
||||||
--timestamp \
|
|
||||||
--options runtime \
|
|
||||||
--entitlements packages/cli/script/entitlements.plist \
|
|
||||||
--sign "$identity" \
|
|
||||||
"$file"
|
|
||||||
codesign --verify --deep --strict --verbose=4 "$file"
|
|
||||||
codesign --display --requirements - "$file"
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$found" -eq 0 ]; then
|
|
||||||
echo "No macOS CLI binaries found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
||||||
with:
|
|
||||||
name: opencode-preview-cli
|
|
||||||
path: packages/cli/dist/cli-*
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
build-node-cli:
|
build-node-cli:
|
||||||
needs: version
|
needs: version
|
||||||
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'beta'
|
if: github.repository == 'anomalyco/opencode'
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -237,7 +184,7 @@ jobs:
|
|||||||
- build-cli
|
- build-cli
|
||||||
- version
|
- version
|
||||||
runs-on: blacksmith-4vcpu-windows-2025
|
runs-on: blacksmith-4vcpu-windows-2025
|
||||||
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'v2' && github.ref_name != 'beta'
|
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'v2'
|
||||||
env:
|
env:
|
||||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
@@ -334,6 +281,7 @@ jobs:
|
|||||||
|
|
||||||
build-electron:
|
build-electron:
|
||||||
needs:
|
needs:
|
||||||
|
- build-cli
|
||||||
- version
|
- version
|
||||||
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'v2'
|
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'v2'
|
||||||
continue-on-error: false
|
continue-on-error: false
|
||||||
@@ -430,14 +378,16 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||||
OPENCODE_CLI_TARGET: ${{ matrix.settings.target }}
|
OPENCODE_CLI_ARTIFACT: ${{ (runner.os == 'Windows' && 'opencode-cli-windows') || 'opencode-cli' }}
|
||||||
|
RUST_TARGET: ${{ matrix.settings.target }}
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: bun run build
|
run: bun run build
|
||||||
working-directory: packages/desktop
|
working-directory: packages/desktop
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=4096
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
|
||||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
||||||
@@ -446,7 +396,6 @@ jobs:
|
|||||||
VITE_SENTRY_DSN: ${{ vars.WEB_SENTRY_DSN }}
|
VITE_SENTRY_DSN: ${{ vars.WEB_SENTRY_DSN }}
|
||||||
VITE_SENTRY_ENVIRONMENT: ${{ (github.ref_name == 'beta' && 'beta') || 'production' }}
|
VITE_SENTRY_ENVIRONMENT: ${{ (github.ref_name == 'beta' && 'beta') || 'production' }}
|
||||||
VITE_SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
VITE_SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
||||||
OPENCODE_CLI_TARGET: ${{ matrix.settings.target }}
|
|
||||||
|
|
||||||
- name: Package
|
- name: Package
|
||||||
if: needs.version.outputs.release
|
if: needs.version.outputs.release
|
||||||
@@ -456,7 +405,8 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||||
CSC_KEYCHAIN: build.keychain
|
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
|
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
APPLE_API_KEY: ${{ runner.temp }}/apple-api-key.p8
|
APPLE_API_KEY: ${{ runner.temp }}/apple-api-key.p8
|
||||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }}
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }}
|
||||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||||
@@ -521,7 +471,6 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- version
|
- version
|
||||||
- build-cli
|
- build-cli
|
||||||
- sign-cli-macos
|
|
||||||
- build-node-cli
|
- build-node-cli
|
||||||
- sign-cli-windows
|
- sign-cli-windows
|
||||||
- build-electron
|
- build-electron
|
||||||
@@ -551,31 +500,29 @@ jobs:
|
|||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
if: github.ref_name != 'v2' && github.ref_name != 'beta'
|
if: github.ref_name != 'v2'
|
||||||
with:
|
with:
|
||||||
name: opencode-cli
|
name: opencode-cli
|
||||||
path: packages/opencode/dist
|
path: packages/opencode/dist
|
||||||
|
|
||||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
if: github.ref_name != 'v2' && github.ref_name != 'beta'
|
if: github.ref_name != 'v2'
|
||||||
with:
|
with:
|
||||||
name: opencode-cli-windows
|
name: opencode-cli-windows
|
||||||
path: packages/opencode/dist
|
path: packages/opencode/dist
|
||||||
|
|
||||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
if: github.ref_name != 'v2' && github.ref_name != 'beta'
|
if: github.ref_name != 'v2'
|
||||||
with:
|
with:
|
||||||
name: opencode-cli-signed-windows
|
name: opencode-cli-signed-windows
|
||||||
path: packages/opencode/dist
|
path: packages/opencode/dist
|
||||||
|
|
||||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
if: github.ref_name != 'beta'
|
|
||||||
with:
|
with:
|
||||||
name: opencode-preview-cli
|
name: opencode-preview-cli
|
||||||
path: packages/cli/dist
|
path: packages/cli/dist
|
||||||
|
|
||||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
if: github.ref_name != 'beta'
|
|
||||||
with:
|
with:
|
||||||
pattern: opencode-node-cli-*
|
pattern: opencode-node-cli-*
|
||||||
path: packages/cli/dist/node
|
path: packages/cli/dist/node
|
||||||
|
|||||||
@@ -55,12 +55,6 @@ jobs:
|
|||||||
git config --global user.email "bot@opencode.ai"
|
git config --global user.email "bot@opencode.ai"
|
||||||
git config --global user.name "opencode"
|
git config --global user.name "opencode"
|
||||||
|
|
||||||
- name: Install ffmpeg
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install --yes ffmpeg
|
|
||||||
|
|
||||||
- name: Cache Turbo
|
- name: Cache Turbo
|
||||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
@@ -72,7 +66,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
run: GITHUB_ACTIONS=false bun turbo test ${{ runner.os == 'Windows' && '--filter=!opencode-drive' || '' }}
|
run: GITHUB_ACTIONS=false bun turbo test
|
||||||
env:
|
env:
|
||||||
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }}
|
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Context } from "../../../packages/plugin/src/tui/context"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
id: "test.tui-discovery-smoke",
|
||||||
|
setup(_context: Context) {
|
||||||
|
// context.ui.toast.show({
|
||||||
|
// title: "TUI plugin discovery works",
|
||||||
|
// message: "Loaded .opencode/plugins/tui/discovery-smoke.ts",
|
||||||
|
// variant: "success",
|
||||||
|
// duration: 30_000,
|
||||||
|
// })
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
name: ideal-pseudocode
|
||||||
|
description: Function-by-function refactoring loop driven by ideal pseudocode. Use when the user says "ideal pseudocode", asks to make a function read like its pseudocode, or wants a dense module cleaned up one function at a time.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Ideal Pseudocode
|
||||||
|
|
||||||
|
Clean up one function at a time by writing the pseudocode it _should_ read as, naming every delta between that and the real code, and closing only the gaps the user approves.
|
||||||
|
|
||||||
|
## Loop
|
||||||
|
|
||||||
|
One function per round. Never touch code before the user picks a direction.
|
||||||
|
|
||||||
|
1. **Pick the target** with the user — usually the next function up or down the call chain from the last round.
|
||||||
|
2. **Read the current code** fresh from disk. It may have unsaved or parallel edits; ask before overwriting anything unexpected.
|
||||||
|
3. **Distill.** Write the function's ideal pseudocode in a `ts`-fenced code block — TypeScript-flavored for syntax highlighting, but pseudocode: comments over mechanics, one line per idea, every arm of a loop visible as an arm. For a dense or unfamiliar function, first show the _current_ structure as pseudocode, then the ideal.
|
||||||
|
4. **Name the deltas.** A numbered list; each delta is one concrete gap: control flow smuggled through combinators, a flag-typed parameter, colliding names, side-effect plumbing drowning a loop body, twin functions differing only by a filter. For each, give an honest cost/benefit and a decisive recommendation — including "leave it" when the density has reasons (state the reasons).
|
||||||
|
5. **Verify before proposing.** Any API the proposal builds on (a combinator, schedule, matcher) gets checked against the installed version's source, not memory.
|
||||||
|
6. **Get buy-in, then apply.** Minimal edits. Quarantine irreducible ugliness inside a small helper whose name is the pseudocode line it implements. Collapse shallow sibling functions into one deep function only when the distinguishing argument is a domain noun, not a behavior switch.
|
||||||
|
7. **Validate.** Typecheck and the focused test suites from the package directory; rerun timing-sensitive tests repeatedly. Flag any semantic change the restructure introduced, even when tests pass.
|
||||||
|
8. **Offer the next target** and stop.
|
||||||
|
|
||||||
|
## Style
|
||||||
|
|
||||||
|
- Keep each round tight: pseudocode block, deltas, recommendation, one question.
|
||||||
|
- State mutation belongs in one visible place (usually the loop tail), never spread across error channels or callback closures.
|
||||||
|
- Prefer early return and `if` chains for control flow; matchers are for producing values.
|
||||||
|
- Compare names against the question each caller is actually asking; rename toward the domain noun.
|
||||||
|
|
||||||
|
## Example round
|
||||||
|
|
||||||
|
Every round should read like this (abridged from a real one, on an Effect step-runner):
|
||||||
|
|
||||||
|
> `runStep` it is. The ideal pseudocode:
|
||||||
|
>
|
||||||
|
> ```ts
|
||||||
|
> // One logical step: one settled model call, however many attempts it takes.
|
||||||
|
> // An attempt can end without settling in two ways:
|
||||||
|
> // transient provider failure -> retry the same call, same assistant message
|
||||||
|
> // compaction rewrote history -> rebuild the request and call again
|
||||||
|
> function runStep(session, promotable, step) {
|
||||||
|
> while (true) {
|
||||||
|
> const result = callModel(session, promotable, step)
|
||||||
|
>
|
||||||
|
> if (result.completed) return result
|
||||||
|
>
|
||||||
|
> if (result.retryable) {
|
||||||
|
> wait(retrySchedule) // exhausted -> publish Step.Failed, fail drain
|
||||||
|
> promotable = none // never re-promote on a second attempt
|
||||||
|
> continue
|
||||||
|
> }
|
||||||
|
>
|
||||||
|
> // compaction restarted the step: fresh request from rewritten history
|
||||||
|
> promotable = none
|
||||||
|
> step = result.step
|
||||||
|
> }
|
||||||
|
> }
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Comparing against the real thing, three deltas:
|
||||||
|
>
|
||||||
|
> 1. **The retry arm doesn't live in the loop.** In the pseudocode, retry is just another loop arm. In the real code it's smuggled through the error channel: `Effect.tapError` mutating loop variables via `Effect.sync` closures, then `Effect.retryOrElse` re-running a suspended call. Four mutable variables mutated from three different places — the state flow is invisible unless you already know the trick.
|
||||||
|
> 2. **`recoverOverflow: typeof compaction.compact | undefined`** — a function-typed parameter used as a one-shot armed/disarmed flag. The callee already closes over `compaction`; a boolean says what it is.
|
||||||
|
> 3. **`assistantMessageID`** exists solely to resume the same durable message across retries — fine, but only legible once the retry arm is visible in the loop.
|
||||||
|
>
|
||||||
|
> The unifying move for (1): a schedule driver, so a retryable failure becomes a caught value handled in the loop body exactly like the compaction arm. One honest trade: it's more manual than the blessed combinator — but the manual version is the loop the combinator was hiding, and the loop already exists for restarts. Two restart mechanisms, one control structure.
|
||||||
|
>
|
||||||
|
> Want me to apply it — unified loop, simplified schedule input, boolean `recoverOverflow`?
|
||||||
@@ -51,7 +51,7 @@ files. If the script is unsuccessful, automatically fix the script and run it ag
|
|||||||
Scripts use one typed definition object. `setup` runs before OpenCode starts,
|
Scripts use one typed definition object. `setup` runs before OpenCode starts,
|
||||||
and `fs.writeFile` always writes inside the simulated project.
|
and `fs.writeFile` always writes inside the simulated project.
|
||||||
|
|
||||||
You can read the full typed API here: https://raw.githubusercontent.com/anomalyco/opencode/v2/packages/drive/src/script/types.ts
|
You can read the full typed API here: https://raw.githubusercontent.com/jlongster/opencode-drive/refs/heads/main/src/script/types.ts
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { defineScript } from "opencode-drive"
|
import { defineScript } from "opencode-drive"
|
||||||
@@ -83,7 +83,7 @@ itself (this is extremely rare, do not use this unless explicitly asked). In thi
|
|||||||
mode `ui` is typed as `null`; call `server.launch()` exactly
|
mode `ui` is typed as `null`; call `server.launch()` exactly
|
||||||
once before launching clients. Each `clients.launch(name)` result provides the
|
once before launching clients. Each `clients.launch(name)` result provides the
|
||||||
same UI methods as the automatic client. You can see an example of this API
|
same UI methods as the automatic client. You can see an example of this API
|
||||||
here: https://raw.githubusercontent.com/anomalyco/opencode/v2/packages/drive/examples/multiple-clients.ts
|
here: https://raw.githubusercontent.com/jlongster/opencode-drive/refs/heads/main/examples/multiple-clients.ts
|
||||||
|
|
||||||
Use the exported `wait(milliseconds)` utility for an unconditional delay.
|
Use the exported `wait(milliseconds)` utility for an unconditional delay.
|
||||||
|
|
||||||
@@ -114,8 +114,8 @@ completion are automatic.
|
|||||||
|
|
||||||
You can see some example scripts here:
|
You can see some example scripts here:
|
||||||
|
|
||||||
- https://raw.githubusercontent.com/anomalyco/opencode/v2/packages/drive/examples/simple.ts
|
- https://raw.githubusercontent.com/jlongster/opencode-drive/refs/heads/main/examples/simple.ts
|
||||||
- https://raw.githubusercontent.com/anomalyco/opencode/v2/packages/drive/examples/serve.ts
|
- https://raw.githubusercontent.com/jlongster/opencode-drive/refs/heads/main/examples/serve.ts
|
||||||
|
|
||||||
## Prune
|
## Prune
|
||||||
|
|
||||||
@@ -251,3 +251,4 @@ opencode-drive stop --name demo
|
|||||||
```bash
|
```bash
|
||||||
opencode-drive dir --name demo
|
opencode-drive dir --name demo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
---
|
|
||||||
name: rtl-aware-development
|
|
||||||
description: OpenCode Desktop should be RTL-aware. Use when implementing or reviewing RTL/LTR behavior in the web app, desktop app, CSS, menus, scrolling, resizing, icons, mixed-direction text, or Electron title bars.
|
|
||||||
---
|
|
||||||
|
|
||||||
# RTL-Aware Development
|
|
||||||
|
|
||||||
Treat direction as independent from language. Test English in both directions as well as real RTL and mixed-script content.
|
|
||||||
|
|
||||||
## Guidelines
|
|
||||||
|
|
||||||
- Set `lang` and `dir` on the document, and propagate direction through component providers used by portaled menus and popovers. Do not change the selected locale merely to force RTL.
|
|
||||||
- Keep DOM and focus order semantic. Flexbox and Grid already follow `dir`; do not add `row-reverse`, CSS `order`, or reversed markup just to mirror a layout.
|
|
||||||
- Prefer logical CSS for semantic layout. Reserve physical coordinates for pointer positions, canvas geometry, native window controls, and other genuinely physical placement.
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* Avoid */
|
|
||||||
padding-left: 12px;
|
|
||||||
right: 0;
|
|
||||||
border-right: 1px solid;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
/* Prefer */
|
|
||||||
padding-inline-start: 12px;
|
|
||||||
inset-inline-end: 0;
|
|
||||||
border-inline-end: 1px solid;
|
|
||||||
text-align: start;
|
|
||||||
```
|
|
||||||
|
|
||||||
- Isolate mixed-direction text. Use `dir="auto"` or `<bdi>` for unknown text; keep code, URLs, IDs, and filesystem paths LTR without forcing the surrounding component LTR.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<span class="file-row"><bdi dir="auto">README.md</bdi></span> <bdi dir="ltr"><code>C:\src\app.ts</code></bdi>
|
|
||||||
```
|
|
||||||
|
|
||||||
- Mirror directional meaning, not every image. Back/forward, previous/next, disclosure, indentation, and directional progress may need mirroring. Do not mirror brands, clocks, media controls, charts, or text. Reverse physical gradients, `translateX`, SVG transforms, and animation deltas explicitly.
|
|
||||||
- Map interactions through direction. `clientX` remains physical; resizing a logical edge needs an RTL-aware delta. Logical previous/next keyboard controls may swap ArrowLeft/ArrowRight. Follow the relevant WAI-ARIA widget pattern.
|
|
||||||
- Do not assume LTR scrolling. RTL `scrollLeft` can start at `0` and become negative. Prefer `scrollIntoView({ inline: "nearest" })` or a tested direction-normalizing helper.
|
|
||||||
- For Electron title bars, prefer native caption controls and use `titleBarOverlay` plus `env(titlebar-area-*)` for the safe content rectangle. Keep Windows/macOS native-control avoidance and `trafficLightPosition` physical; keep app navigation inside that rectangle logical. Mark interactive titlebar children `app-region: no-drag`.
|
|
||||||
- Verify behavior, not screenshots alone. Check computed styles, pseudo-element geometry, hit zones, focus order, keyboard behavior, submenu direction, zoom/scaling, and both LTR and RTL scroll endpoints.
|
|
||||||
|
|
||||||
## Test Matrix
|
|
||||||
|
|
||||||
- English + LTR
|
|
||||||
- English + forced RTL
|
|
||||||
- A real RTL locale + RTL
|
|
||||||
- Mixed RTL/LTR content, long labels, numbers, code, and paths
|
|
||||||
- Keyboard, pointer resize, scrolling, menus/submenus, and Electron titlebar controls in both directions
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [RTL Styling 101, Ahmad Shadeed](https://rtlstyling.com/posts/rtl-styling/)
|
|
||||||
- [CSS-Tricks: RTL Styling 101](https://css-tricks.com/rtl-styling-101/)
|
|
||||||
- [CSS-Tricks: CSS Logical Properties and Values](https://css-tricks.com/css-logical-properties-and-values/)
|
|
||||||
- [W3C: Structural markup and right-to-left text](https://www.w3.org/International/questions/qa-html-dir)
|
|
||||||
- [W3C: Inline bidirectional markup](https://www.w3.org/International/articles/inline-bidi-markup/)
|
|
||||||
- [MDN: CSS logical properties and values](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Logical_properties_and_values)
|
|
||||||
- [MDN: `dir`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/dir)
|
|
||||||
- [MDN: `scrollLeft`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft)
|
|
||||||
- [web.dev: Logical properties](https://web.dev/learn/css/logical-properties/)
|
|
||||||
- [Electron: Custom title bar](https://www.electronjs.org/docs/latest/tutorial/custom-title-bar)
|
|
||||||
- [WAI-ARIA: Window splitter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/)
|
|
||||||
- [Kobalte: I18n Provider](https://kobalte.dev/docs/core/components/i18n-provider/)
|
|
||||||
@@ -4,7 +4,7 @@ import { tool } from "@opencode-ai/plugin"
|
|||||||
const TEAM = {
|
const TEAM = {
|
||||||
tui: ["kommander", "simonklee"],
|
tui: ["kommander", "simonklee"],
|
||||||
desktop_web: ["Hona", "Brendonovich"],
|
desktop_web: ["Hona", "Brendonovich"],
|
||||||
core: ["jlongster", "rekram1-node", "neriousy", "nexxeln", "kitlangton"],
|
core: ["jlongster", "rekram1-node", "nexxeln", "kitlangton"],
|
||||||
inference: ["fwang", "MrMushrooooom", "starptech"],
|
inference: ["fwang", "MrMushrooooom", "starptech"],
|
||||||
windows: ["Hona"],
|
windows: ["Hona"],
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
- After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly.
|
- After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly.
|
||||||
- Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server.
|
- Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server.
|
||||||
- Do not modify `packages/opencode` unless the user explicitly asks for V1 work. `packages/opencode` is the V1 implementation and is present for reference only. New implementation changes should land in the V2 package set: `packages/core`, `packages/cli`, `packages/server`, `packages/protocol`, `packages/schema`, and related generated client surfaces when required.
|
- Do not modify `packages/opencode` unless the user explicitly asks for V1 work. `packages/opencode` is the V1 implementation and is present for reference only. New implementation changes should land in the V2 package set: `packages/core`, `packages/cli`, `packages/server`, `packages/protocol`, `packages/schema`, and related generated client surfaces when required.
|
||||||
- The default branch in this repo is `v2`.
|
- The default branch in this repo is `dev`.
|
||||||
- Base all new branches and worktrees on `v2`, or `origin/v2` when the local `v2` ref is unavailable. Do not base them on `dev`.
|
- Local `main` ref may not exist; use `dev` or `origin/dev` for diffs.
|
||||||
- Local `main` ref may not exist; use `v2` or `origin/v2` for diffs.
|
|
||||||
|
|
||||||
## Live V2 TUI Testing
|
## Live V2 TUI Testing
|
||||||
|
|
||||||
@@ -171,4 +170,4 @@ const table = sqliteTable("session", {
|
|||||||
- One step is one logical LLM call; its durable record covers only the model-visible span. Do not write "provider turn", and do not use bare "turn" for a single call: "turn" is reserved for the future assistant-turn unit containing all steps from prompt promotion until the session would go idle.
|
- One step is one logical LLM call; its durable record covers only the model-visible span. Do not write "provider turn", and do not use bare "turn" for a single call: "turn" is reserved for the future assistant-turn unit containing all steps from prompt promotion until the session would go idle.
|
||||||
- Keep EventV2 replay owner claims separate from clustered Session execution ownership.
|
- Keep EventV2 replay owner claims separate from clustered Session execution ownership.
|
||||||
- Keep the Instructions algebra and built-ins in `src/instructions`; keep instruction producers with their observed domains, and keep Session History selection plus `InstructionState` and `InstructionEntry` persistence Session-owned. `InstructionDiscovery` observes ambient global and upward-project instructions. The runner composes built-ins, discovery, guidance, and entries explicitly in `loadInstructions`; there is no instruction registry.
|
- Keep the Instructions algebra and built-ins in `src/instructions`; keep instruction producers with their observed domains, and keep Session History selection plus `InstructionState` and `InstructionEntry` persistence Session-owned. `InstructionDiscovery` observes ambient global and upward-project instructions. The runner composes built-ins, discovery, guidance, and entries explicitly in `loadInstructions`; there is no instruction registry.
|
||||||
- `session.instructions.updated` stores only changed source keys and content hashes. Blob values live once in `instruction_blob`; `instruction_state` is a rebuildable fold cache, never primary state. Render initial instructions and chronological updates from values during request assembly. Completed compaction moves the instruction epoch; Session movement retains it so destination instruction changes are chronological, while committed revert clears it. Unavailable sources retain the last value and block only the initial complete delta.
|
- `session.instructions.updated` stores only changed source keys and content hashes. Blob values live once in `instruction_blob`; `instruction_state` is a rebuildable fold cache, never primary state. Render initial instructions and chronological updates from values during request assembly. Completed compaction moves the instruction epoch; Session movement and committed revert clear it. Unavailable sources retain the last value and block only the initial complete delta.
|
||||||
|
|||||||
@@ -252,6 +252,33 @@ These are not strictly enforced, they are just general guidelines:
|
|||||||
|
|
||||||
For net-new functionality, start with a design conversation. Open an issue describing the problem, your proposed approach (optional), and why it belongs in OpenCode. The core team will help decide whether it should move forward; please wait for that approval instead of opening a feature PR directly.
|
For net-new functionality, start with a design conversation. Open an issue describing the problem, your proposed approach (optional), and why it belongs in OpenCode. The core team will help decide whether it should move forward; please wait for that approval instead of opening a feature PR directly.
|
||||||
|
|
||||||
|
## Trust & Vouch System
|
||||||
|
|
||||||
|
This project uses [vouch](https://github.com/mitchellh/vouch) to manage contributor trust. The vouch list is maintained in [`.github/VOUCHED.td`](.github/VOUCHED.td).
|
||||||
|
|
||||||
|
### How it works
|
||||||
|
|
||||||
|
- **Vouched users** are explicitly trusted contributors.
|
||||||
|
- **Denounced users** are explicitly blocked. Issues and pull requests from denounced users are automatically closed. If you have been denounced, you can request to be unvouched by reaching out to a maintainer on [Discord](https://opencode.ai/discord)
|
||||||
|
- **Everyone else** can participate normally — you don't need to be vouched to open issues or PRs.
|
||||||
|
|
||||||
|
### For maintainers
|
||||||
|
|
||||||
|
Collaborators with write access can manage the vouch list by commenting on any issue:
|
||||||
|
|
||||||
|
- `vouch` — vouch for the issue author
|
||||||
|
- `vouch @username` — vouch for a specific user
|
||||||
|
- `denounce` — denounce the issue author
|
||||||
|
- `denounce @username` — denounce a specific user
|
||||||
|
- `denounce @username <reason>` — denounce with a reason
|
||||||
|
- `unvouch` / `unvouch @username` — remove someone from the list
|
||||||
|
|
||||||
|
Changes are committed automatically to `.github/VOUCHED.td`.
|
||||||
|
|
||||||
|
### Denouncement policy
|
||||||
|
|
||||||
|
Denouncement is reserved for users who repeatedly submit low-quality AI-generated contributions, spam, or otherwise act in bad faith. It is not used for disagreements or honest mistakes.
|
||||||
|
|
||||||
## Issue Requirements
|
## Issue Requirements
|
||||||
|
|
||||||
All issues **must** use one of our issue templates:
|
All issues **must** use one of our issue templates:
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB |
+2
-6
@@ -288,12 +288,8 @@ new sst.cloudflare.x.SolidStart("Console", {
|
|||||||
server: {
|
server: {
|
||||||
placement: { region: "aws:us-east-2" },
|
placement: { region: "aws:us-east-2" },
|
||||||
transform: {
|
transform: {
|
||||||
worker: (args) => {
|
worker: {
|
||||||
args.compatibilityFlags = $resolve(args.compatibilityFlags).apply((flags) => [
|
tailConsumers: [{ service: logProcessor.nodes.worker.scriptName }],
|
||||||
...(flags ?? []),
|
|
||||||
"global_fetch_strictly_public",
|
|
||||||
])
|
|
||||||
args.tailConsumers = [{ service: logProcessor.nodes.worker.scriptName }]
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
APP=opencode2
|
APP=opencode
|
||||||
|
|
||||||
MUTED='\033[0;2m'
|
MUTED='\033[0;2m'
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
@@ -15,14 +15,14 @@ Usage: install.sh [options]
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Display this help message
|
-h, --help Display this help message
|
||||||
-v, --version <version> Install a specific version (e.g., 0.0.0-next-17236)
|
-v, --version <version> Install a specific version (e.g., 1.0.180)
|
||||||
-b, --binary <path> Install from a local binary instead of downloading
|
-b, --binary <path> Install from a local binary instead of downloading
|
||||||
--no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.)
|
--no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.)
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
curl -fsSL https://raw.githubusercontent.com/anomalyco/opencode/v2/install | bash
|
curl -fsSL https://opencode.ai/install | bash
|
||||||
curl -fsSL https://raw.githubusercontent.com/anomalyco/opencode/v2/install | bash -s -- --version 0.0.0-next-17236
|
curl -fsSL https://opencode.ai/install | bash -s -- --version 1.0.180
|
||||||
./install --binary /path/to/opencode2
|
./install --binary /path/to/opencode
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +109,11 @@ else
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
archive_ext=".zip"
|
||||||
|
if [ "$os" = "linux" ]; then
|
||||||
|
archive_ext=".tar.gz"
|
||||||
|
fi
|
||||||
|
|
||||||
is_musl=false
|
is_musl=false
|
||||||
if [ "$os" = "linux" ]; then
|
if [ "$os" = "linux" ]; then
|
||||||
if [ -f /etc/alpine-release ]; then
|
if [ -f /etc/alpine-release ]; then
|
||||||
@@ -160,42 +165,42 @@ else
|
|||||||
target="$target-musl"
|
target="$target-musl"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! command -v tar >/dev/null 2>&1; then
|
filename="$APP-$target$archive_ext"
|
||||||
echo -e "${RED}Error: 'tar' is required but not installed.${NC}"
|
|
||||||
exit 1
|
|
||||||
|
if [ "$os" = "linux" ]; then
|
||||||
|
if ! command -v tar >/dev/null 2>&1; then
|
||||||
|
echo -e "${RED}Error: 'tar' is required but not installed.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! command -v unzip >/dev/null 2>&1; then
|
||||||
|
echo -e "${RED}Error: 'unzip' is required but not installed.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$requested_version" ]; then
|
if [ -z "$requested_version" ]; then
|
||||||
metadata=$(curl -fsSL https://registry.npmjs.org/@opencode-ai%2fcli/next || true)
|
url="https://github.com/anomalyco/opencode/releases/latest/download/$filename"
|
||||||
specific_version=$(echo "$metadata" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p')
|
specific_version=$(curl -s https://api.github.com/repos/anomalyco/opencode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
|
||||||
|
|
||||||
if [ -z "$specific_version" ]; then
|
if [[ $? -ne 0 || -z "$specific_version" ]]; then
|
||||||
echo -e "${RED}Failed to fetch version information${NC}"
|
echo -e "${RED}Failed to fetch version information${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# Strip leading 'v' if present
|
# Strip leading 'v' if present
|
||||||
requested_version="${requested_version#v}"
|
requested_version="${requested_version#v}"
|
||||||
|
url="https://github.com/anomalyco/opencode/releases/download/v${requested_version}/$filename"
|
||||||
specific_version=$requested_version
|
specific_version=$requested_version
|
||||||
fi
|
|
||||||
|
|
||||||
package_name="@opencode-ai/cli-$target"
|
# Verify the release exists before downloading
|
||||||
http_status=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/@opencode-ai%2fcli-$target/$specific_version" || true)
|
http_status=$(curl -sI -o /dev/null -w "%{http_code}" "https://github.com/anomalyco/opencode/releases/tag/v${requested_version}")
|
||||||
if [ "$http_status" = "404" ]; then
|
if [ "$http_status" = "404" ]; then
|
||||||
echo -e "${RED}Error: Version ${specific_version} is not available for $target${NC}"
|
echo -e "${RED}Error: Release v${requested_version} not found${NC}"
|
||||||
echo -e "${MUTED}Available versions: https://www.npmjs.com/package/$package_name?activeTab=versions${NC}"
|
echo -e "${MUTED}Available releases: https://github.com/anomalyco/opencode/releases${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [ "$http_status" != "200" ]; then
|
|
||||||
echo -e "${RED}Failed to fetch package information${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
filename="cli-$target-$specific_version.tgz"
|
|
||||||
url="https://registry.npmjs.org/$package_name/-/$filename"
|
|
||||||
binary_name="$APP"
|
|
||||||
if [ "$os" = "windows" ]; then
|
|
||||||
binary_name="$APP.exe"
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -214,13 +219,11 @@ print_message() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
check_version() {
|
check_version() {
|
||||||
if command -v "$APP" >/dev/null 2>&1; then
|
if command -v opencode >/dev/null 2>&1; then
|
||||||
opencode_path=$(which "$APP")
|
opencode_path=$(which opencode)
|
||||||
|
|
||||||
## Check the installed version
|
## Check the installed version
|
||||||
installed_version=$("$APP" --version 2>/dev/null || echo "")
|
installed_version=$(opencode --version 2>/dev/null || echo "")
|
||||||
installed_version="${installed_version##* }"
|
|
||||||
installed_version="${installed_version#v}"
|
|
||||||
|
|
||||||
if [[ "$installed_version" != "$specific_version" ]]; then
|
if [[ "$installed_version" != "$specific_version" ]]; then
|
||||||
print_message info "${MUTED}Installed version: ${NC}$installed_version."
|
print_message info "${MUTED}Installed version: ${NC}$installed_version."
|
||||||
@@ -284,7 +287,7 @@ download_with_progress() {
|
|||||||
trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN
|
trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN
|
||||||
|
|
||||||
(
|
(
|
||||||
curl --trace-ascii "$tracefile" -fsL -o "$output" "$url"
|
curl --trace-ascii "$tracefile" -s -L -o "$output" "$url"
|
||||||
) &
|
) &
|
||||||
local curl_pid=$!
|
local curl_pid=$!
|
||||||
|
|
||||||
@@ -322,25 +325,30 @@ download_with_progress() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
download_and_install() {
|
download_and_install() {
|
||||||
print_message info "\n${MUTED}Installing ${NC}$APP ${MUTED}version: ${NC}$specific_version"
|
print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}version: ${NC}$specific_version"
|
||||||
local tmp_dir="${TMPDIR:-/tmp}/opencode_install_$$"
|
local tmp_dir="${TMPDIR:-/tmp}/opencode_install_$$"
|
||||||
mkdir -p "$tmp_dir"
|
mkdir -p "$tmp_dir"
|
||||||
|
|
||||||
if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then
|
if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then
|
||||||
# Fallback to standard curl on Windows, non-TTY environments, or if custom progress fails
|
# Fallback to standard curl on Windows, non-TTY environments, or if custom progress fails
|
||||||
curl -f -# -L -o "$tmp_dir/$filename" "$url"
|
curl -# -L -o "$tmp_dir/$filename" "$url"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"
|
if [ "$os" = "linux" ]; then
|
||||||
mv "$tmp_dir/package/bin/$binary_name" "$INSTALL_DIR"
|
tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"
|
||||||
chmod 755 "${INSTALL_DIR}/$binary_name"
|
else
|
||||||
|
unzip -q "$tmp_dir/$filename" -d "$tmp_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$tmp_dir/opencode" "$INSTALL_DIR"
|
||||||
|
chmod 755 "${INSTALL_DIR}/opencode"
|
||||||
rm -rf "$tmp_dir"
|
rm -rf "$tmp_dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
install_from_binary() {
|
install_from_binary() {
|
||||||
print_message info "\n${MUTED}Installing ${NC}$APP ${MUTED}from: ${NC}$binary_path"
|
print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}from: ${NC}$binary_path"
|
||||||
cp "$binary_path" "${INSTALL_DIR}/$APP"
|
cp "$binary_path" "${INSTALL_DIR}/opencode"
|
||||||
chmod 755 "${INSTALL_DIR}/$APP"
|
chmod 755 "${INSTALL_DIR}/opencode"
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ -n "$binary_path" ]; then
|
if [ -n "$binary_path" ]; then
|
||||||
@@ -445,8 +453,8 @@ echo -e ""
|
|||||||
echo -e "${MUTED}OpenCode includes free models, to start:${NC}"
|
echo -e "${MUTED}OpenCode includes free models, to start:${NC}"
|
||||||
echo -e ""
|
echo -e ""
|
||||||
echo -e "cd <project> ${MUTED}# Open directory${NC}"
|
echo -e "cd <project> ${MUTED}# Open directory${NC}"
|
||||||
echo -e "opencode2 ${MUTED}# Run command${NC}"
|
echo -e "opencode ${MUTED}# Run command${NC}"
|
||||||
echo -e ""
|
echo -e ""
|
||||||
echo -e "${MUTED}For more information visit ${NC}https://opencode.ai/v2/docs"
|
echo -e "${MUTED}For more information visit ${NC}https://opencode.ai/docs"
|
||||||
echo -e ""
|
echo -e ""
|
||||||
echo -e ""
|
echo -e ""
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-uduwrM143NDSc+tXsi4lVVfoMll2a3BDHRUjuO7GB68=",
|
"x86_64-linux": "sha256-RFek0QoEEjsgbqmTE/SxQAmPtYyzs0IPR2ugFn5Okrs=",
|
||||||
"aarch64-linux": "sha256-6DUda78XdXY6DP86lIUkweSjys3iG4Y4mo1PiaNuXbg=",
|
"aarch64-linux": "sha256-BmAxapY1YrAFn7mVq3/6A9+6Au5UIvSqBboHMkyJH3I=",
|
||||||
"aarch64-darwin": "sha256-AkJwfLULLZVwwz+XU1QcFUZoIS7oVPCn+n/MXEaxrqE=",
|
"aarch64-darwin": "sha256-Sx3bGWQqLlgoa/RudJxanjSzhFRNklckT2ffnO2I5F4=",
|
||||||
"x86_64-darwin": "sha256-hAxKGdiITTxQ2uujQt6prNjo3NxGAMMeo+9HlMWK6GU="
|
"x86_64-darwin": "sha256-CMOhiisHNowg06qadvgg4K+60zrynglwiT0qKYQ4NiA="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-13
@@ -33,7 +33,6 @@
|
|||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
"packages/console/*",
|
"packages/console/*",
|
||||||
"packages/lab/*",
|
|
||||||
"packages/stats/*",
|
"packages/stats/*",
|
||||||
"packages/slack"
|
"packages/slack"
|
||||||
],
|
],
|
||||||
@@ -47,9 +46,9 @@
|
|||||||
"@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.5.2",
|
"@opentui/core": "0.4.5",
|
||||||
"@opentui/keymap": "0.5.2",
|
"@opentui/keymap": "0.4.5",
|
||||||
"@opentui/solid": "0.5.2",
|
"@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",
|
||||||
"@standard-schema/spec": "1.1.0",
|
"@standard-schema/spec": "1.1.0",
|
||||||
@@ -79,7 +78,7 @@
|
|||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
"get-east-asian-width": "1.6.0",
|
"get-east-asian-width": "1.6.0",
|
||||||
"luxon": "3.6.1",
|
"luxon": "3.6.1",
|
||||||
"marked": "18.0.7",
|
"marked": "17.0.6",
|
||||||
"marked-shiki": "1.2.1",
|
"marked-shiki": "1.2.1",
|
||||||
"remend": "1.3.0",
|
"remend": "1.3.0",
|
||||||
"@playwright/test": "1.59.1",
|
"@playwright/test": "1.59.1",
|
||||||
@@ -101,7 +100,6 @@
|
|||||||
"@sentry/solid": "10.36.0",
|
"@sentry/solid": "10.36.0",
|
||||||
"@sentry/vite-plugin": "4.6.0",
|
"@sentry/vite-plugin": "4.6.0",
|
||||||
"solid-js": "1.9.10",
|
"solid-js": "1.9.10",
|
||||||
"solid-sonner": "0.3.1",
|
|
||||||
"vite-plugin-solid": "2.11.10",
|
"vite-plugin-solid": "2.11.10",
|
||||||
"@lydell/node-pty": "1.2.0-beta.12"
|
"@lydell/node-pty": "1.2.0-beta.12"
|
||||||
}
|
}
|
||||||
@@ -121,8 +119,7 @@
|
|||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
"sst": "catalog:",
|
"sst": "catalog:",
|
||||||
"turbo": "2.10.2",
|
"turbo": "2.10.2"
|
||||||
"vitest": "4.1.10"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.933.0",
|
"@aws-sdk/client-s3": "3.933.0",
|
||||||
@@ -160,8 +157,6 @@
|
|||||||
"effect": "catalog:"
|
"effect": "catalog:"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"@ai-sdk/openai-compatible@2.0.41": "patches/@ai-sdk%2Fopenai-compatible@2.0.41.patch",
|
|
||||||
"@dnd-kit/dom@0.5.0": "patches/@dnd-kit%2Fdom@0.5.0.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",
|
||||||
"@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",
|
||||||
@@ -173,8 +168,6 @@
|
|||||||
"@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.101": "patches/effect@4.0.0-beta.101.patch",
|
"effect@4.0.0-beta.101": "patches/effect@4.0.0-beta.101.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"
|
||||||
"@cloudflare/vitest-pool-workers@0.12.6": "patches/@cloudflare%2Fvitest-pool-workers@0.12.6.patch",
|
|
||||||
"@ff-labs/fff-bun@0.10.1": "patches/@ff-labs%2Ffff-bun@0.10.1.patch"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -368,12 +368,11 @@ Other provider exports listed above remain direct facades until they explicitly
|
|||||||
|
|
||||||
## Provider options & HTTP overlays
|
## Provider options & HTTP overlays
|
||||||
|
|
||||||
Request options in order of stability:
|
Three escape hatches in order of stability:
|
||||||
|
|
||||||
1. **`generation`** — portable knobs (`maxTokens`, `temperature`, `topP`, `topK`, penalties, seed, stop).
|
1. **`generation`** — portable knobs (`maxTokens`, `temperature`, `topP`, `topK`, penalties, seed, stop).
|
||||||
2. **`promptCacheKey`** — stable cache affinity lowered by every protocol that supports it.
|
2. **`providerOptions: { <provider>: {...} }`** — typed-at-the-facade provider-specific knobs (OpenAI `promptCacheKey`, Anthropic `thinking`, Gemini `thinkingConfig`, OpenRouter routing).
|
||||||
3. **`providerOptions: { <provider>: {...} }`** — typed-at-the-facade provider-specific knobs (OpenAI `store`, Anthropic `thinking`, Gemini `thinkingConfig`, OpenRouter routing).
|
3. **`http: { body, headers, query }`** — last-resort serializable overlays merged into the final HTTP request. Reach for this only when a stable typed path doesn't yet exist.
|
||||||
4. **`http: { body, headers, query }`** — last-resort serializable overlays merged into the final HTTP request. Reach for this only when a stable typed path doesn't yet exist.
|
|
||||||
|
|
||||||
Route/provider defaults are overridden by request-level values for each axis.
|
Route/provider defaults are overridden by request-level values for each axis.
|
||||||
|
|
||||||
|
|||||||
@@ -33,10 +33,9 @@ const model = OpenAI.configure({
|
|||||||
//
|
//
|
||||||
// - `generation`: common controls such as max tokens, temperature, topP/topK,
|
// - `generation`: common controls such as max tokens, temperature, topP/topK,
|
||||||
// penalties, seed, and stop sequences.
|
// penalties, seed, and stop sequences.
|
||||||
// - `promptCacheKey`: stable cache affinity for protocols that support it.
|
|
||||||
// - `providerOptions`: namespaced provider-native behavior. For example,
|
// - `providerOptions`: namespaced provider-native behavior. For example,
|
||||||
// OpenAI store behavior, Anthropic thinking, Gemini thinking config, or
|
// OpenAI cache keys and store behavior, Anthropic thinking, Gemini thinking
|
||||||
// OpenRouter routing/reasoning.
|
// config, or OpenRouter routing/reasoning.
|
||||||
// - `http`: last-resort serializable overlays for final request body, headers,
|
// - `http`: last-resort serializable overlays for final request body, headers,
|
||||||
// and query params. Prefer typed `providerOptions` when a field is stable.
|
// and query params. Prefer typed `providerOptions` when a field is stable.
|
||||||
//
|
//
|
||||||
@@ -46,7 +45,9 @@ const request = LLM.request({
|
|||||||
system: "You are concise and practical.",
|
system: "You are concise and practical.",
|
||||||
prompt: "Tell me a joke",
|
prompt: "Tell me a joke",
|
||||||
generation: { maxTokens: 80, temperature: 0.7 },
|
generation: { maxTokens: 80, temperature: 0.7 },
|
||||||
promptCacheKey: "tutorial-joke",
|
providerOptions: {
|
||||||
|
openai: { promptCacheKey: "tutorial-joke" },
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 3. `generate` sends the request and collects the event stream into one
|
// 3. `generate` sends the request and collects the event stream into one
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as fs from "node:fs/promises"
|
|||||||
import * as path from "node:path"
|
import * as path from "node:path"
|
||||||
|
|
||||||
const RECORDINGS_DIR = path.resolve(import.meta.dir, "..", "test", "fixtures", "recordings")
|
const RECORDINGS_DIR = path.resolve(import.meta.dir, "..", "test", "fixtures", "recordings")
|
||||||
const MODELS_DEV_URL = "https://models.opencode.ai/api.json"
|
const MODELS_DEV_URL = "https://models.dev/api.json"
|
||||||
|
|
||||||
type JsonRecord = Record<string, unknown>
|
type JsonRecord = Record<string, unknown>
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
//
|
//
|
||||||
// Manual `cache: CacheHint` placements on individual parts are preserved and
|
// Manual `cache: CacheHint` placements on individual parts are preserved and
|
||||||
// count against the four-breakpoint budget; auto only fills remaining slots.
|
// count against the four-breakpoint budget; auto only fills remaining slots.
|
||||||
import { CacheHint, type CachePolicy, type CachePolicyObject } from "./schema/options.js"
|
import { CacheHint, type CachePolicy, type CachePolicyObject } from "./schema/options"
|
||||||
import { LLMRequest, Message, ToolDefinition, type ContentPart } from "./schema/messages.js"
|
import { LLMRequest, Message, ToolDefinition, type ContentPart } from "./schema/messages"
|
||||||
|
|
||||||
const AUTO: CachePolicyObject = {
|
const AUTO: CachePolicyObject = {
|
||||||
tools: true,
|
tools: true,
|
||||||
@@ -133,8 +133,7 @@ const countHints = (request: LLMRequest) =>
|
|||||||
|
|
||||||
export const applyCachePolicy = (request: LLMRequest): LLMRequest => {
|
export const applyCachePolicy = (request: LLMRequest): LLMRequest => {
|
||||||
if (!RESPECTS_INLINE_HINTS.has(request.model.route.id)) return request
|
if (!RESPECTS_INLINE_HINTS.has(request.model.route.id)) return request
|
||||||
if (request.model.route.id === "openrouter" && (request.cache === undefined || request.cache === "auto"))
|
if (request.model.route.id === "openrouter" && (request.cache === undefined || request.cache === "auto")) return request
|
||||||
return request
|
|
||||||
const policy = resolve(request.cache)
|
const policy = resolve(request.cache)
|
||||||
if (!policy.tools && !policy.system && !policy.messages) return request
|
if (!policy.tools && !policy.system && !policy.messages) return request
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Context, Effect, Layer } from "effect"
|
import { Context, Effect, Layer } from "effect"
|
||||||
import { RequestExecutor } from "./route/executor.js"
|
import { RequestExecutor } from "./route/executor"
|
||||||
import type { ImageOptions, ImageRequest, ImageRequestFor, ImageResponse } from "./image.js"
|
import type { ImageOptions, ImageRequest, ImageRequestFor, ImageResponse } from "./image"
|
||||||
import type { AIError } from "./schema/index.js"
|
import type { AIError } from "./schema"
|
||||||
|
|
||||||
export type Execute = RequestExecutor.Interface["execute"]
|
export type Execute = RequestExecutor.Interface["execute"]
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
import { Effect, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import {
|
import { HttpOptions, InvalidRequestReason, AIError, ModelID, ProviderID, ProviderMetadata, Usage } from "./schema"
|
||||||
HttpOptions,
|
import { ImageClient, Service, type Execute as ImageExecute } from "./image-client"
|
||||||
InvalidRequestReason,
|
|
||||||
AIError,
|
|
||||||
ModelID,
|
|
||||||
ProviderID,
|
|
||||||
ProviderMetadata,
|
|
||||||
Usage,
|
|
||||||
} from "./schema/index.js"
|
|
||||||
import { ImageClient, Service, type Execute as ImageExecute } from "./image-client.js"
|
|
||||||
|
|
||||||
export interface ImageRoute<Options extends ImageOptions = ImageOptions> {
|
export interface ImageRoute<Options extends ImageOptions = ImageOptions> {
|
||||||
readonly id: string
|
readonly id: string
|
||||||
|
|||||||
+18
-21
@@ -1,22 +1,22 @@
|
|||||||
export { LLMClient } from "./route/client.js"
|
export { LLMClient } from "./route/client"
|
||||||
export { ImageClient } from "./image-client.js"
|
export { ImageClient } from "./image-client"
|
||||||
export { Auth } from "./route/auth.js"
|
export { Auth } from "./route/auth"
|
||||||
export { Provider } from "./provider.js"
|
export { Provider } from "./provider"
|
||||||
export { ProviderPackage } from "./provider-package.js"
|
export { ProviderPackage } from "./provider-package"
|
||||||
export { isContextOverflow, isContextOverflowFailure } from "./provider-error.js"
|
export { isContextOverflow, isContextOverflowFailure } from "./provider-error"
|
||||||
export type {
|
export type {
|
||||||
RouteLanguageModelInput,
|
RouteLanguageModelInput,
|
||||||
RouteRoutedLanguageModelInput,
|
RouteRoutedLanguageModelInput,
|
||||||
Interface as LLMClientShape,
|
Interface as LLMClientShape,
|
||||||
Service as LLMClientService,
|
Service as LLMClientService,
|
||||||
} from "./route/client.js"
|
} from "./route/client"
|
||||||
export * from "./schema/index.js"
|
export * from "./schema"
|
||||||
export { GeneratedImage, ImageInput, ImageInputSchema, ImageModel, ImageRequest, ImageResponse } from "./image.js"
|
export { GeneratedImage, ImageInput, ImageInputSchema, ImageModel, ImageRequest, ImageResponse } from "./image"
|
||||||
export type { ImageModelOptions, ImageOptions, ImageRequestFor, ImageRequestInput, ImageRoute } from "./image.js"
|
export type { ImageModelOptions, ImageOptions, ImageRequestFor, ImageRequestInput, ImageRoute } from "./image"
|
||||||
export { Image } from "./image.js"
|
export { Image } from "./image"
|
||||||
export { Tool, ToolFailure, toDefinitions } from "./tool.js"
|
export { Tool, ToolFailure, toDefinitions } from "./tool"
|
||||||
export { ToolRuntime } from "./tool-runtime.js"
|
export { ToolRuntime } from "./tool-runtime"
|
||||||
export type { DispatchResult as ToolDispatchResult, ToolSettlement } from "./tool-runtime.js"
|
export type { DispatchResult as ToolDispatchResult, ToolSettlement } from "./tool-runtime"
|
||||||
export type {
|
export type {
|
||||||
AnyExecutableTool,
|
AnyExecutableTool,
|
||||||
AnyTool,
|
AnyTool,
|
||||||
@@ -29,14 +29,11 @@ export type {
|
|||||||
Tools,
|
Tools,
|
||||||
ToolSchema,
|
ToolSchema,
|
||||||
ToolToModelOutput,
|
ToolToModelOutput,
|
||||||
} from "./tool.js"
|
} from "./tool"
|
||||||
export * as LLM from "./llm.js"
|
export * as LLM from "./llm"
|
||||||
export type {
|
export type {
|
||||||
Definition as ProviderDefinition,
|
Definition as ProviderDefinition,
|
||||||
LanguageModelFactory as ProviderLanguageModelFactory,
|
LanguageModelFactory as ProviderLanguageModelFactory,
|
||||||
LanguageModelOptions as ProviderLanguageModelOptions,
|
LanguageModelOptions as ProviderLanguageModelOptions,
|
||||||
} from "./provider.js"
|
} from "./provider"
|
||||||
export type {
|
export type { Definition as ProviderPackageDefinition, Settings as ProviderPackageSettings } from "./provider-package"
|
||||||
Definition as ProviderPackageDefinition,
|
|
||||||
Settings as ProviderPackageSettings,
|
|
||||||
} from "./provider-package.js"
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Effect, JsonSchema, Schema } from "effect"
|
import { Effect, JsonSchema, Schema } from "effect"
|
||||||
import { LLMClient, Service } from "./route/client.js"
|
import { LLMClient, Service } from "./route/client"
|
||||||
import {
|
import {
|
||||||
GenerationOptions,
|
GenerationOptions,
|
||||||
HttpOptions,
|
HttpOptions,
|
||||||
@@ -15,8 +15,8 @@ import {
|
|||||||
ToolDefinition,
|
ToolDefinition,
|
||||||
type ContentPart,
|
type ContentPart,
|
||||||
type LanguageModelProviderOptions,
|
type LanguageModelProviderOptions,
|
||||||
} from "./schema/index.js"
|
} from "./schema"
|
||||||
import { make as makeTool, toDefinitions, type ToolSchema } from "./tool.js"
|
import { make as makeTool, toDefinitions, type ToolSchema } from "./tool"
|
||||||
|
|
||||||
/** Input accepted by `LLM.request`, normalized into the canonical `LLMRequest` class. */
|
/** Input accepted by `LLM.request`, normalized into the canonical `LLMRequest` class. */
|
||||||
export type RequestInput<SelectedLanguageModel extends LanguageModel = LanguageModel> = Omit<
|
export type RequestInput<SelectedLanguageModel extends LanguageModel = LanguageModel> = Omit<
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from "./protocols/index.js"
|
export * from "./protocols/index"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Effect, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { Tool } from "@opencode-ai/schema/tool"
|
import { Tool } from "@opencode-ai/schema/tool"
|
||||||
import { Route } from "../route/client.js"
|
import { Route } from "../route/client"
|
||||||
import { Auth } from "../route/auth.js"
|
import { Auth } from "../route/auth"
|
||||||
import { Endpoint } from "../route/endpoint.js"
|
import { Endpoint } from "../route/endpoint"
|
||||||
import { Framing } from "../route/framing.js"
|
import { Framing } from "../route/framing"
|
||||||
import { Protocol } from "../route/protocol.js"
|
import { Protocol } from "../route/protocol"
|
||||||
import {
|
import {
|
||||||
AIError,
|
AIError,
|
||||||
LLMEvent,
|
LLMEvent,
|
||||||
@@ -21,13 +21,13 @@ import {
|
|||||||
type ToolCallPart,
|
type ToolCallPart,
|
||||||
type ToolDefinition,
|
type ToolDefinition,
|
||||||
type ToolResultPart,
|
type ToolResultPart,
|
||||||
} from "../schema/index.js"
|
} from "../schema"
|
||||||
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js"
|
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
|
||||||
import { classifyProviderFailure } from "../provider-error.js"
|
import { classifyProviderFailure } from "../provider-error"
|
||||||
import * as Cache from "./utils/cache.js"
|
import * as Cache from "./utils/cache"
|
||||||
import { Lifecycle } from "./utils/lifecycle.js"
|
import { Lifecycle } from "./utils/lifecycle"
|
||||||
import { ToolSchemaProjection } from "./utils/tool-schema.js"
|
import { ToolSchemaProjection } from "./utils/tool-schema"
|
||||||
import { ToolStream } from "./utils/tool-stream.js"
|
import { ToolStream } from "./utils/tool-stream"
|
||||||
|
|
||||||
const ADAPTER = "anthropic-messages"
|
const ADAPTER = "anthropic-messages"
|
||||||
const MEDIA_MIMES = new Set<string>([...ProviderShared.IMAGE_MIMES, ...ProviderShared.PDF_MIMES])
|
const MEDIA_MIMES = new Set<string>([...ProviderShared.IMAGE_MIMES, ...ProviderShared.PDF_MIMES])
|
||||||
@@ -422,12 +422,14 @@ const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (part: Me
|
|||||||
|
|
||||||
// Tool results may carry structured text, images, and documents. Keep media as provider-native
|
// Tool results may carry structured text, images, and documents. Keep media as provider-native
|
||||||
// content instead of JSON-stringifying base64 into a prompt string.
|
// content instead of JSON-stringifying base64 into a prompt string.
|
||||||
const lowerToolResultContentItem = Effect.fnUntraced(function* (item: Tool.Content) {
|
const lowerToolResultContentItem = Effect.fn("AnthropicMessages.lowerToolResultContentItem")(function* (
|
||||||
|
item: Tool.Content,
|
||||||
|
) {
|
||||||
if (item.type === "text") return { type: "text" as const, text: item.text } satisfies AnthropicTextBlock
|
if (item.type === "text") return { type: "text" as const, text: item.text } satisfies AnthropicTextBlock
|
||||||
return yield* lowerMedia({ type: "media", mediaType: item.mime, data: item.uri, filename: item.name })
|
return yield* lowerMedia({ type: "media", mediaType: item.mime, data: item.uri, filename: item.name })
|
||||||
})
|
})
|
||||||
|
|
||||||
const lowerToolResultContent = Effect.fnUntraced(function* (part: ToolResultPart) {
|
const lowerToolResultContent = Effect.fn("AnthropicMessages.lowerToolResultContent")(function* (part: ToolResultPart) {
|
||||||
// Text / json / error results stay as a string for backward compatibility
|
// Text / json / error results stay as a string for backward compatibility
|
||||||
// with existing cassettes and provider expectations.
|
// with existing cassettes and provider expectations.
|
||||||
if (part.result.type !== "content") return ProviderShared.toolResultText(part)
|
if (part.result.type !== "content") return ProviderShared.toolResultText(part)
|
||||||
@@ -573,10 +575,7 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (
|
|||||||
cache_control: cacheControl(breakpoints, part.cache),
|
cache_control: cacheControl(breakpoints, part.cache),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const previous = messages.at(-1)
|
messages.push({ role: "user", content })
|
||||||
if (previous?.role === "user" && previous.content.every((block) => block.type === "tool_result"))
|
|
||||||
messages[messages.length - 1] = { role: "user", content: [...previous.content, ...content] }
|
|
||||||
else messages.push({ role: "user", content })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages
|
return messages
|
||||||
@@ -1032,4 +1031,4 @@ export const route = Route.make({
|
|||||||
headers: () => ({ "anthropic-version": "2023-06-01" }),
|
headers: () => ({ "anthropic-version": "2023-06-01" }),
|
||||||
})
|
})
|
||||||
|
|
||||||
export * as AnthropicMessages from "./anthropic-messages.js"
|
export * as AnthropicMessages from "./anthropic-messages"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Effect, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { Route } from "../route/client.js"
|
import { Route } from "../route/client"
|
||||||
import { Endpoint } from "../route/endpoint.js"
|
import { Endpoint } from "../route/endpoint"
|
||||||
import { Protocol } from "../route/protocol.js"
|
import { Protocol } from "../route/protocol"
|
||||||
import {
|
import {
|
||||||
AIError,
|
AIError,
|
||||||
LLMEvent,
|
LLMEvent,
|
||||||
@@ -17,20 +17,20 @@ import {
|
|||||||
type ToolCallPart,
|
type ToolCallPart,
|
||||||
type ToolDefinition,
|
type ToolDefinition,
|
||||||
type ToolResultPart,
|
type ToolResultPart,
|
||||||
} from "../schema/index.js"
|
} from "../schema"
|
||||||
import { BedrockEventStream } from "./bedrock-event-stream.js"
|
import { BedrockEventStream } from "./bedrock-event-stream"
|
||||||
import { classifyProviderFailure } from "../provider-error.js"
|
import { classifyProviderFailure } from "../provider-error"
|
||||||
import { JsonObject, optionalArray, ProviderShared } from "./shared.js"
|
import { JsonObject, optionalArray, ProviderShared } from "./shared"
|
||||||
import { BedrockAuth } from "./utils/bedrock-auth.js"
|
import { BedrockAuth } from "./utils/bedrock-auth"
|
||||||
import { BedrockCache } from "./utils/bedrock-cache.js"
|
import { BedrockCache } from "./utils/bedrock-cache"
|
||||||
import { BedrockMedia } from "./utils/bedrock-media.js"
|
import { BedrockMedia } from "./utils/bedrock-media"
|
||||||
import { Lifecycle } from "./utils/lifecycle.js"
|
import { Lifecycle } from "./utils/lifecycle"
|
||||||
import { ToolSchemaProjection } from "./utils/tool-schema.js"
|
import { ToolSchemaProjection } from "./utils/tool-schema"
|
||||||
import { ToolStream } from "./utils/tool-stream.js"
|
import { ToolStream } from "./utils/tool-stream"
|
||||||
|
|
||||||
const ADAPTER = "bedrock-converse"
|
const ADAPTER = "bedrock-converse"
|
||||||
|
|
||||||
export type { Credentials as BedrockCredentials } from "./utils/bedrock-auth.js"
|
export type { Credentials as BedrockCredentials } from "./utils/bedrock-auth"
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Request Body Schema
|
// Request Body Schema
|
||||||
@@ -348,10 +348,7 @@ const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const previous = messages.at(-1)
|
messages.push({ role: "user", content })
|
||||||
if (previous?.role === "user")
|
|
||||||
messages[messages.length - 1] = { role: "user", content: [...previous.content, ...content] }
|
|
||||||
else messages.push({ role: "user", content })
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,10 +392,7 @@ const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (
|
|||||||
const cachePoint = BedrockCache.block(breakpoints, part.cache)
|
const cachePoint = BedrockCache.block(breakpoints, part.cache)
|
||||||
if (cachePoint) content.push(cachePoint)
|
if (cachePoint) content.push(cachePoint)
|
||||||
}
|
}
|
||||||
const previous = messages.at(-1)
|
messages.push({ role: "user", content })
|
||||||
if (previous?.role === "user")
|
|
||||||
messages[messages.length - 1] = { role: "user", content: [...previous.content, ...content] }
|
|
||||||
else messages.push({ role: "user", content })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages
|
return messages
|
||||||
@@ -737,4 +731,4 @@ export const route = Route.make({
|
|||||||
|
|
||||||
export const sigV4Auth = BedrockAuth.sigV4
|
export const sigV4Auth = BedrockAuth.sigV4
|
||||||
|
|
||||||
export * as BedrockConverse from "./bedrock-converse.js"
|
export * as BedrockConverse from "./bedrock-converse"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { EventStreamCodec } from "@smithy/eventstream-codec"
|
import { EventStreamCodec } from "@smithy/eventstream-codec"
|
||||||
import { fromUtf8, toUtf8 } from "@smithy/util-utf8"
|
import { fromUtf8, toUtf8 } from "@smithy/util-utf8"
|
||||||
import { Effect, Stream } from "effect"
|
import { Effect, Stream } from "effect"
|
||||||
import { Framing } from "../route/framing.js"
|
import { Framing } from "../route/framing"
|
||||||
import { ProviderShared } from "./shared.js"
|
import { ProviderShared } from "./shared"
|
||||||
|
|
||||||
// Bedrock streams responses using the AWS event stream binary protocol — each
|
// Bedrock streams responses using the AWS event stream binary protocol — each
|
||||||
// frame is `[length:4][headers-length:4][prelude-crc:4][headers][payload][crc:4]`.
|
// frame is `[length:4][headers-length:4][prelude-crc:4][headers][payload][crc:4]`.
|
||||||
@@ -98,4 +98,4 @@ export const framing = (route: string): Framing.Definition<object> => ({
|
|||||||
frame: (bytes) => bytes.pipe(Stream.mapAccumEffect(() => initialFrameBuffer, consumeFrames(route))),
|
frame: (bytes) => bytes.pipe(Stream.mapAccumEffect(() => initialFrameBuffer, consumeFrames(route))),
|
||||||
})
|
})
|
||||||
|
|
||||||
export * as BedrockEventStream from "./bedrock-event-stream.js"
|
export * as BedrockEventStream from "./bedrock-event-stream"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Effect, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { Tool } from "@opencode-ai/schema/tool"
|
import { Tool } from "@opencode-ai/schema/tool"
|
||||||
import { Route } from "../route/client.js"
|
import { Route } from "../route/client"
|
||||||
import { Auth } from "../route/auth.js"
|
import { Auth } from "../route/auth"
|
||||||
import { Endpoint } from "../route/endpoint.js"
|
import { Endpoint } from "../route/endpoint"
|
||||||
import { Framing } from "../route/framing.js"
|
import { Framing } from "../route/framing"
|
||||||
import { Protocol } from "../route/protocol.js"
|
import { Protocol } from "../route/protocol"
|
||||||
import {
|
import {
|
||||||
LLMEvent,
|
LLMEvent,
|
||||||
Usage,
|
Usage,
|
||||||
@@ -17,28 +17,16 @@ import {
|
|||||||
type TextPart,
|
type TextPart,
|
||||||
type ToolCallPart,
|
type ToolCallPart,
|
||||||
type ToolDefinition,
|
type ToolDefinition,
|
||||||
} from "../schema/index.js"
|
} from "../schema"
|
||||||
import { JsonObject, optionalArray, ProviderShared } from "./shared.js"
|
import { JsonObject, optionalArray, ProviderShared } from "./shared"
|
||||||
import { GeminiToolSchema } from "./utils/gemini-tool-schema.js"
|
import { GeminiToolSchema } from "./utils/gemini-tool-schema"
|
||||||
import { Lifecycle } from "./utils/lifecycle.js"
|
import { Lifecycle } from "./utils/lifecycle"
|
||||||
import { ToolSchemaProjection } from "./utils/tool-schema.js"
|
import { ToolSchemaProjection } from "./utils/tool-schema"
|
||||||
|
|
||||||
const ADAPTER = "gemini"
|
const ADAPTER = "gemini"
|
||||||
const MEDIA_MIMES = new Set<string>(ProviderShared.MEDIA_MIMES)
|
const MEDIA_MIMES = new Set<string>(ProviderShared.MEDIA_MIMES)
|
||||||
// Google documents this sentinel for replaying Gemini 3 function calls after their original signature was lost.
|
|
||||||
const SKIP_THOUGHT_SIGNATURE_VALIDATOR = "skip_thought_signature_validator"
|
|
||||||
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"
|
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"
|
||||||
|
|
||||||
// Gemini 3 rejects replayed function calls without a thought signature. Google's SDKs avoid that in normal chats by
|
|
||||||
// retaining complete model responses, but OpenCode reconstructs durable history and may encounter an unsigned call
|
|
||||||
// from an older or external session. Model IDs are open-ended, so unknown Gemini aliases inherit current behavior.
|
|
||||||
const requiresThoughtSignatureFallback = (modelID: string) => {
|
|
||||||
if (!/(^|\/)gemini-/i.test(modelID)) return false
|
|
||||||
if (/(^|\/)gemini-(?:1|2)(?:[.-]|$)/i.test(modelID)) return false
|
|
||||||
if (/(^|\/)gemini-pro(?:-vision)?$/i.test(modelID)) return false
|
|
||||||
return !/(^|\/)gemini-robotics-er-1\.5(?:[.-]|$)/i.test(modelID)
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OptionsInput {
|
export interface OptionsInput {
|
||||||
readonly [key: string]: unknown
|
readonly [key: string]: unknown
|
||||||
readonly cachedContent?: string
|
readonly cachedContent?: string
|
||||||
@@ -157,9 +145,6 @@ const GeminiGenerationConfig = Schema.Struct({
|
|||||||
temperature: Schema.optional(Schema.Number),
|
temperature: Schema.optional(Schema.Number),
|
||||||
topP: Schema.optional(Schema.Number),
|
topP: Schema.optional(Schema.Number),
|
||||||
topK: Schema.optional(Schema.Number),
|
topK: Schema.optional(Schema.Number),
|
||||||
frequencyPenalty: Schema.optional(Schema.Number),
|
|
||||||
presencePenalty: Schema.optional(Schema.Number),
|
|
||||||
seed: Schema.optional(Schema.Number),
|
|
||||||
stopSequences: optionalArray(Schema.String),
|
stopSequences: optionalArray(Schema.String),
|
||||||
thinkingConfig: Schema.optional(GeminiThinkingConfig),
|
thinkingConfig: Schema.optional(GeminiThinkingConfig),
|
||||||
})
|
})
|
||||||
@@ -217,13 +202,11 @@ interface ParserState {
|
|||||||
// keys on non-object scalars. Mirrors OpenCode's historical Gemini rules.
|
// keys on non-object scalars. Mirrors OpenCode's historical Gemini rules.
|
||||||
//
|
//
|
||||||
// 2. Project — lossy mapping from JSON Schema to Gemini's schema dialect:
|
// 2. Project — lossy mapping from JSON Schema to Gemini's schema dialect:
|
||||||
// drop empty root parameter schemas while preserving nested empty objects,
|
// drop empty objects, derive `nullable: true` from `type: [..., "null"]`,
|
||||||
// expand type arrays into `anyOf`, derive `nullable: true` from null members,
|
// coerce `const` to `[const]` enum, recurse properties/items, propagate
|
||||||
// coerce `const` to `[const]` enum, recurse properties/items, and propagate
|
|
||||||
// only an allowlisted set of keys (description, required, format, type,
|
// only an allowlisted set of keys (description, required, format, type,
|
||||||
// nullable, enum, properties, items, allOf, anyOf, oneOf, minLength).
|
// properties, items, allOf, anyOf, oneOf, minLength). Anything outside the
|
||||||
// Anything outside the allowlist (e.g. `additionalProperties`, `$ref`) is
|
// allowlist (e.g. `additionalProperties`, `$ref`) is silently dropped.
|
||||||
// silently dropped.
|
|
||||||
//
|
//
|
||||||
// Sanitize runs first, then project. The implementation lives in
|
// Sanitize runs first, then project. The implementation lives in
|
||||||
// `utils/gemini-tool-schema` so this protocol keeps the same shape as the other
|
// `utils/gemini-tool-schema` so this protocol keeps the same shape as the other
|
||||||
@@ -299,8 +282,6 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request: LLMR
|
|||||||
|
|
||||||
if (message.role === "assistant") {
|
if (message.role === "assistant") {
|
||||||
const parts: Array<Schema.Schema.Type<typeof GeminiContentPart>> = []
|
const parts: Array<Schema.Schema.Type<typeof GeminiContentPart>> = []
|
||||||
// Parallel Gemini 3 calls may carry one signature on the first call; unsigned sibling calls are valid.
|
|
||||||
let hasSignedToolCall = false
|
|
||||||
for (const part of message.content) {
|
for (const part of message.content) {
|
||||||
if (!ProviderShared.supportsContent(part, ["text", "reasoning", "tool-call"]))
|
if (!ProviderShared.supportsContent(part, ["text", "reasoning", "tool-call"]))
|
||||||
return yield* ProviderShared.unsupportedContent("Gemini", "assistant", ["text", "reasoning", "tool-call"])
|
return yield* ProviderShared.unsupportedContent("Gemini", "assistant", ["text", "reasoning", "tool-call"])
|
||||||
@@ -313,17 +294,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request: LLMR
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (part.type === "tool-call") {
|
if (part.type === "tool-call") {
|
||||||
const lowered = lowerToolCall(part)
|
parts.push(lowerToolCall(part))
|
||||||
const signature = lowered.thoughtSignature
|
|
||||||
parts.push({
|
|
||||||
...lowered,
|
|
||||||
thoughtSignature:
|
|
||||||
signature ??
|
|
||||||
(requiresThoughtSignatureFallback(request.model.id) && !hasSignedToolCall
|
|
||||||
? SKIP_THOUGHT_SIGNATURE_VALIDATOR
|
|
||||||
: undefined),
|
|
||||||
})
|
|
||||||
if (signature !== undefined) hasSignedToolCall = true
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,9 +388,6 @@ const fromRequest = Effect.fn("Gemini.fromRequest")(function* (request: LLMReque
|
|||||||
temperature: generation?.temperature,
|
temperature: generation?.temperature,
|
||||||
topP: generation?.topP,
|
topP: generation?.topP,
|
||||||
topK: generation?.topK,
|
topK: generation?.topK,
|
||||||
frequencyPenalty: generation?.frequencyPenalty,
|
|
||||||
presencePenalty: generation?.presencePenalty,
|
|
||||||
seed: generation?.seed,
|
|
||||||
stopSequences: generation?.stop,
|
stopSequences: generation?.stop,
|
||||||
thinkingConfig: options.thinkingConfig,
|
thinkingConfig: options.thinkingConfig,
|
||||||
}
|
}
|
||||||
@@ -643,4 +611,4 @@ export const route = Route.make({
|
|||||||
framing: Framing.sse,
|
framing: Framing.sse,
|
||||||
})
|
})
|
||||||
|
|
||||||
export * as Gemini from "./gemini.js"
|
export * as Gemini from "./gemini"
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import {
|
|||||||
type ImageInput,
|
type ImageInput,
|
||||||
type ImageRequestFor,
|
type ImageRequestFor,
|
||||||
type ImageRoute,
|
type ImageRoute,
|
||||||
} from "../image.js"
|
} from "../image"
|
||||||
import { Auth, type Definition as AuthDefinition } from "../route/auth.js"
|
import { Auth, type Definition as AuthDefinition } from "../route/auth"
|
||||||
import {
|
import {
|
||||||
InvalidProviderOutputReason,
|
InvalidProviderOutputReason,
|
||||||
AIError,
|
AIError,
|
||||||
@@ -17,9 +17,9 @@ import {
|
|||||||
mergeJsonRecords,
|
mergeJsonRecords,
|
||||||
type HttpOptions,
|
type HttpOptions,
|
||||||
type ProviderMetadata,
|
type ProviderMetadata,
|
||||||
} from "../schema/index.js"
|
} from "../schema"
|
||||||
import { ProviderShared } from "./shared.js"
|
import { ProviderShared } from "./shared"
|
||||||
import { ImageInputs } from "./utils/image-input.js"
|
import { ImageInputs } from "./utils/image-input"
|
||||||
|
|
||||||
const ADAPTER = "google-images"
|
const ADAPTER = "google-images"
|
||||||
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"
|
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
export * as AnthropicMessages from "./anthropic-messages.js"
|
export * as AnthropicMessages from "./anthropic-messages"
|
||||||
export * as BedrockConverse from "./bedrock-converse.js"
|
export * as BedrockConverse from "./bedrock-converse"
|
||||||
export * as Gemini from "./gemini.js"
|
export * as Gemini from "./gemini"
|
||||||
export * as OpenAIChat from "./openai-chat.js"
|
export * as OpenAIChat from "./openai-chat"
|
||||||
export * as OpenAIImages from "./openai-images.js"
|
export * as OpenAIImages from "./openai-images"
|
||||||
export * as OpenAICompatibleChat from "./openai-compatible-chat.js"
|
export * as OpenAICompatibleChat from "./openai-compatible-chat"
|
||||||
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js"
|
export * as OpenAICompatibleResponses from "./openai-compatible-responses"
|
||||||
export * as OpenAIResponses from "./openai-responses.js"
|
export * as OpenAIResponses from "./openai-responses"
|
||||||
export * as OpenResponses from "./open-responses.js"
|
export * as OpenResponses from "./open-responses"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Effect, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import type { Content } from "@opencode-ai/schema/tool"
|
import type { Content } from "@opencode-ai/schema/tool"
|
||||||
import { HttpTransport } from "../route/transport/index.js"
|
import { HttpTransport } from "../route/transport"
|
||||||
import { Protocol } from "../route/protocol.js"
|
import { Protocol } from "../route/protocol"
|
||||||
import {
|
import {
|
||||||
AIError,
|
AIError,
|
||||||
LLMEvent,
|
LLMEvent,
|
||||||
@@ -16,13 +16,13 @@ import {
|
|||||||
type ToolCallPart,
|
type ToolCallPart,
|
||||||
type ToolDefinition,
|
type ToolDefinition,
|
||||||
type ToolResultPart,
|
type ToolResultPart,
|
||||||
} from "../schema/index.js"
|
} from "../schema"
|
||||||
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js"
|
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
|
||||||
import { classifyProviderFailure } from "../provider-error.js"
|
import { classifyProviderFailure } from "../provider-error"
|
||||||
import { OpenResponsesOptions } from "./utils/open-responses-options.js"
|
import { OpenResponsesOptions } from "./utils/open-responses-options"
|
||||||
import { Lifecycle } from "./utils/lifecycle.js"
|
import { Lifecycle } from "./utils/lifecycle"
|
||||||
import { ToolSchemaProjection } from "./utils/tool-schema.js"
|
import { ToolSchemaProjection } from "./utils/tool-schema"
|
||||||
import { ToolStream } from "./utils/tool-stream.js"
|
import { ToolStream } from "./utils/tool-stream"
|
||||||
|
|
||||||
const ADAPTER = "open-responses"
|
const ADAPTER = "open-responses"
|
||||||
const NAME = "Open Responses"
|
const NAME = "Open Responses"
|
||||||
@@ -358,7 +358,7 @@ const lowerMedia = Effect.fn("OpenResponses.lowerMedia")(function* (
|
|||||||
return { type: "input_image" as const, image_url: media.dataUrl }
|
return { type: "input_image" as const, image_url: media.dataUrl }
|
||||||
})
|
})
|
||||||
|
|
||||||
const lowerUserContent = Effect.fnUntraced(function* (
|
const lowerUserContent = Effect.fn("OpenResponses.lowerUserContent")(function* (
|
||||||
part: LLMRequest["messages"][number]["content"][number],
|
part: LLMRequest["messages"][number]["content"][number],
|
||||||
request: LLMRequest,
|
request: LLMRequest,
|
||||||
extension: Extension,
|
extension: Extension,
|
||||||
@@ -370,7 +370,7 @@ const lowerUserContent = Effect.fnUntraced(function* (
|
|||||||
|
|
||||||
// Tool results may carry structured text, images, and files. Keep media as provider-native
|
// Tool results may carry structured text, images, and files. Keep media as provider-native
|
||||||
// content instead of JSON-stringifying base64 into a prompt string.
|
// content instead of JSON-stringifying base64 into a prompt string.
|
||||||
const lowerToolResultContentItem = Effect.fnUntraced(function* (
|
const lowerToolResultContentItem = Effect.fn("OpenResponses.lowerToolResultContentItem")(function* (
|
||||||
item: Content,
|
item: Content,
|
||||||
request: LLMRequest,
|
request: LLMRequest,
|
||||||
extension: Extension,
|
extension: Extension,
|
||||||
@@ -383,7 +383,7 @@ const lowerToolResultContentItem = Effect.fnUntraced(function* (
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const lowerToolResultOutput = Effect.fnUntraced(function* (
|
const lowerToolResultOutput = Effect.fn("OpenResponses.lowerToolResultOutput")(function* (
|
||||||
part: ToolResultPart,
|
part: ToolResultPart,
|
||||||
request: LLMRequest,
|
request: LLMRequest,
|
||||||
extension: Extension,
|
extension: Extension,
|
||||||
@@ -539,7 +539,7 @@ const lowerOptions = (request: LLMRequest) => {
|
|||||||
return {
|
return {
|
||||||
...(options.instructions ? { instructions: options.instructions } : {}),
|
...(options.instructions ? { instructions: options.instructions } : {}),
|
||||||
...(options.store !== undefined ? { store: options.store } : {}),
|
...(options.store !== undefined ? { store: options.store } : {}),
|
||||||
...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
|
...(options.promptCacheKey ? { prompt_cache_key: options.promptCacheKey } : {}),
|
||||||
...(options.include ? { include: options.include } : {}),
|
...(options.include ? { include: options.include } : {}),
|
||||||
...(options.reasoningEffort || options.reasoningSummary
|
...(options.reasoningEffort || options.reasoningSummary
|
||||||
? { reasoning: { effort: options.reasoningEffort, summary: options.reasoningSummary } }
|
? { reasoning: { effort: options.reasoningEffort, summary: options.reasoningSummary } }
|
||||||
@@ -1061,4 +1061,4 @@ export const protocol = Protocol.make({
|
|||||||
|
|
||||||
export const httpTransport = HttpTransport.sseJson.with<OpenResponsesBody>()
|
export const httpTransport = HttpTransport.sseJson.with<OpenResponsesBody>()
|
||||||
|
|
||||||
export * as OpenResponses from "./open-responses.js"
|
export * as OpenResponses from "./open-responses"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Effect, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { Tool } from "@opencode-ai/schema/tool"
|
import { Tool } from "@opencode-ai/schema/tool"
|
||||||
import { Route } from "../route/client.js"
|
import { Route } from "../route/client"
|
||||||
import { Auth } from "../route/auth.js"
|
import { Auth } from "../route/auth"
|
||||||
import { Endpoint } from "../route/endpoint.js"
|
import { Endpoint } from "../route/endpoint"
|
||||||
import { HttpTransport } from "../route/transport/index.js"
|
import { HttpTransport } from "../route/transport"
|
||||||
import { Protocol } from "../route/protocol.js"
|
import { Protocol } from "../route/protocol"
|
||||||
import {
|
import {
|
||||||
AIError,
|
AIError,
|
||||||
LLMEvent,
|
LLMEvent,
|
||||||
@@ -19,13 +19,13 @@ import {
|
|||||||
type TextPart,
|
type TextPart,
|
||||||
type ToolCallPart,
|
type ToolCallPart,
|
||||||
type ToolDefinition,
|
type ToolDefinition,
|
||||||
} from "../schema/index.js"
|
} from "../schema"
|
||||||
import { classifyProviderFailure } from "../provider-error.js"
|
import { classifyProviderFailure } from "../provider-error"
|
||||||
import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js"
|
import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
|
||||||
import { OpenAIOptions } from "./utils/openai-options.js"
|
import { OpenAIOptions } from "./utils/openai-options"
|
||||||
import { Lifecycle } from "./utils/lifecycle.js"
|
import { Lifecycle } from "./utils/lifecycle"
|
||||||
import { ToolSchemaProjection } from "./utils/tool-schema.js"
|
import { ToolSchemaProjection } from "./utils/tool-schema"
|
||||||
import { ToolStream } from "./utils/tool-stream.js"
|
import { ToolStream } from "./utils/tool-stream"
|
||||||
|
|
||||||
const ADAPTER = "openai-chat"
|
const ADAPTER = "openai-chat"
|
||||||
const IMAGE_MIMES = new Set<string>(ProviderShared.IMAGE_MIMES)
|
const IMAGE_MIMES = new Set<string>(ProviderShared.IMAGE_MIMES)
|
||||||
@@ -132,7 +132,6 @@ export const bodyFields = {
|
|||||||
stream: Schema.Literal(true),
|
stream: Schema.Literal(true),
|
||||||
stream_options: Schema.optional(Schema.Struct({ include_usage: Schema.Boolean })),
|
stream_options: Schema.optional(Schema.Struct({ include_usage: Schema.Boolean })),
|
||||||
store: Schema.optional(Schema.Boolean),
|
store: Schema.optional(Schema.Boolean),
|
||||||
prompt_cache_key: Schema.optional(Schema.String),
|
|
||||||
reasoning_effort: Schema.optional(OpenAIOptions.OpenAIReasoningEffort),
|
reasoning_effort: Schema.optional(OpenAIOptions.OpenAIReasoningEffort),
|
||||||
max_completion_tokens: Schema.optional(Schema.Number),
|
max_completion_tokens: Schema.optional(Schema.Number),
|
||||||
max_tokens: Schema.optional(Schema.Number),
|
max_tokens: Schema.optional(Schema.Number),
|
||||||
@@ -371,13 +370,12 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func
|
|||||||
return text
|
return text
|
||||||
})()
|
})()
|
||||||
const cached = message.content.findLast((part) => "cache" in part && part.cache !== undefined)
|
const cached = message.content.findLast((part) => "cache" in part && part.cache !== undefined)
|
||||||
const cacheControl = options.cacheControl?.(cached && "cache" in cached ? cached.cache : undefined)
|
|
||||||
const result = {
|
const result = {
|
||||||
role: "assistant" as const,
|
role: "assistant" as const,
|
||||||
content: content.length > 0 ? content.map((part) => part.text).join("") : toolCalls.length > 0 ? null : "",
|
content: content.length === 0 ? null : ProviderShared.joinText(content),
|
||||||
...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
|
tool_calls: toolCalls.length === 0 ? undefined : toolCalls,
|
||||||
...(details !== undefined ? { reasoning_details: details } : {}),
|
reasoning_details: details,
|
||||||
...(cacheControl !== undefined ? { cache_control: cacheControl } : {}),
|
cache_control: options.cacheControl?.(cached && "cache" in cached ? cached.cache : undefined),
|
||||||
}
|
}
|
||||||
if (field === undefined || reasoningText === undefined) return result
|
if (field === undefined || reasoningText === undefined) return result
|
||||||
return { ...result, [field]: reasoningText }
|
return { ...result, [field]: reasoningText }
|
||||||
@@ -511,7 +509,6 @@ const lowerOptions = (request: LLMRequest) => {
|
|||||||
const options = OpenAIOptions.resolve(request)
|
const options = OpenAIOptions.resolve(request)
|
||||||
return {
|
return {
|
||||||
...(options.store !== undefined ? { store: options.store } : {}),
|
...(options.store !== undefined ? { store: options.store } : {}),
|
||||||
...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
|
|
||||||
...(options.reasoningEffort ? { reasoning_effort: options.reasoningEffort } : {}),
|
...(options.reasoningEffort ? { reasoning_effort: options.reasoningEffort } : {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -710,20 +707,23 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
|
|||||||
Boolean(delta?.content) ||
|
Boolean(delta?.content) ||
|
||||||
reasoning !== undefined ||
|
reasoning !== undefined ||
|
||||||
(Array.isArray(delta?.reasoning_details) && delta.reasoning_details.length > 0) ||
|
(Array.isArray(delta?.reasoning_details) && delta.reasoning_details.length > 0) ||
|
||||||
toolDeltas.some((tool) => Boolean(tool.id) || Boolean(tool.function?.name) || Boolean(tool.function?.arguments))
|
toolDeltas.some(
|
||||||
|
(tool) => Boolean(tool.id) || Boolean(tool.function?.name) || Boolean(tool.function?.arguments),
|
||||||
|
)
|
||||||
if (state.finishReason !== undefined) {
|
if (state.finishReason !== undefined) {
|
||||||
if (hasLateContent)
|
if (hasLateContent)
|
||||||
return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat received content after the finish reason")
|
return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat received content after the finish reason")
|
||||||
return [{ ...state, usage }, events] as const
|
return [{ ...state, usage }, events] as const
|
||||||
}
|
}
|
||||||
|
|
||||||
const reasoningField = state.reasoningField ?? reasoning?.field
|
const reasoningField = state.reasoningField ?? (!state.lifecycle.text.has("text-0") ? reasoning?.field : undefined)
|
||||||
const detailDelta = Array.isArray(delta?.reasoning_details) ? delta.reasoning_details : undefined
|
const detailDelta = Array.isArray(delta?.reasoning_details) ? delta.reasoning_details : undefined
|
||||||
if (detailDelta !== undefined) appendReasoningDetails(state.reasoningDetails, detailDelta)
|
if (detailDelta !== undefined) appendReasoningDetails(state.reasoningDetails, detailDelta)
|
||||||
const reasoningDetailsObserved = state.reasoningDetailsObserved || detailDelta !== undefined
|
const reasoningDetailsObserved = state.reasoningDetailsObserved || detailDelta !== undefined
|
||||||
const deltaMetadata = reasoningMetadata(reasoningField)
|
const deltaMetadata = reasoningMetadata(reasoningField)
|
||||||
const text = detailDelta?.length ? (detailText(detailDelta) ?? reasoning?.text) : reasoning?.text
|
const text = detailDelta?.length ? (detailText(detailDelta) ?? reasoning?.text) : reasoning?.text
|
||||||
if (text !== undefined) lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", text, deltaMetadata)
|
if (!state.lifecycle.text.has("text-0") && text !== undefined)
|
||||||
|
lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", text, deltaMetadata)
|
||||||
else if (
|
else if (
|
||||||
reasoningDetailsObserved &&
|
reasoningDetailsObserved &&
|
||||||
!lifecycle.reasoning.has("reasoning-0") &&
|
!lifecycle.reasoning.has("reasoning-0") &&
|
||||||
@@ -749,7 +749,8 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
|
|||||||
const fallback = toolDeltas.length > 1 ? position : (latestToolIndex ?? position)
|
const fallback = toolDeltas.length > 1 ? position : (latestToolIndex ?? position)
|
||||||
const fallbackTool = tools[fallback] ?? pendingTools[fallback]
|
const fallbackTool = tools[fallback] ?? pendingTools[fallback]
|
||||||
const index =
|
const index =
|
||||||
tool.index ?? matched ?? (tool.id && fallbackTool?.id && fallbackTool.id !== tool.id ? nextToolIndex : fallback)
|
tool.index ?? matched ??
|
||||||
|
(tool.id && fallbackTool?.id && fallbackTool.id !== tool.id ? nextToolIndex : fallback)
|
||||||
const current = tools[index]
|
const current = tools[index]
|
||||||
const pending = pendingTools[index]
|
const pending = pendingTools[index]
|
||||||
const id = current?.id ?? pending?.id ?? (tool.id || undefined)
|
const id = current?.id ?? pending?.id ?? (tool.id || undefined)
|
||||||
@@ -812,18 +813,14 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
|
|||||||
|
|
||||||
const finishEvents = (state: ParserState): ReadonlyArray<LLMEvent> => {
|
const finishEvents = (state: ParserState): ReadonlyArray<LLMEvent> => {
|
||||||
const events: LLMEvent[] = []
|
const events: LLMEvent[] = []
|
||||||
const toolCallEvents =
|
const hasToolCalls = state.toolCallEvents.length > 0
|
||||||
state.finishReason === undefined && Object.keys(state.tools).length > 0
|
|
||||||
? Effect.runSync(ToolStream.finishAll(ADAPTER, state.tools)).events
|
|
||||||
: state.toolCallEvents
|
|
||||||
const hasToolCalls = toolCallEvents.length > 0
|
|
||||||
const reason = state.finishReason
|
const reason = state.finishReason
|
||||||
? {
|
? {
|
||||||
...state.finishReason,
|
...state.finishReason,
|
||||||
normalized:
|
normalized:
|
||||||
state.finishReason.normalized === "stop" && hasToolCalls ? "tool-calls" : state.finishReason.normalized,
|
state.finishReason.normalized === "stop" && hasToolCalls ? "tool-calls" : state.finishReason.normalized,
|
||||||
}
|
}
|
||||||
: { normalized: hasToolCalls ? ("tool-calls" as const) : ("unknown" as const) }
|
: undefined
|
||||||
const metadata = reasoningMetadata(
|
const metadata = reasoningMetadata(
|
||||||
state.reasoningField,
|
state.reasoningField,
|
||||||
state.reasoningDetailsObserved ? state.reasoningDetails : undefined,
|
state.reasoningDetailsObserved ? state.reasoningDetails : undefined,
|
||||||
@@ -833,9 +830,9 @@ const finishEvents = (state: ParserState): ReadonlyArray<LLMEvent> => {
|
|||||||
? Lifecycle.reasoningStart(state.lifecycle, events, "reasoning-0", reasoningMetadata(state.reasoningField))
|
? Lifecycle.reasoningStart(state.lifecycle, events, "reasoning-0", reasoningMetadata(state.reasoningField))
|
||||||
: state.lifecycle
|
: state.lifecycle
|
||||||
const ended = Lifecycle.reasoningEnd(started, events, "reasoning-0", metadata)
|
const ended = Lifecycle.reasoningEnd(started, events, "reasoning-0", metadata)
|
||||||
const lifecycle = toolCallEvents.length ? Lifecycle.stepStart(ended, events) : ended
|
const lifecycle = state.toolCallEvents.length ? Lifecycle.stepStart(ended, events) : ended
|
||||||
events.push(...toolCallEvents)
|
events.push(...state.toolCallEvents)
|
||||||
Lifecycle.finish(lifecycle, events, { reason, usage: state.usage })
|
if (reason) Lifecycle.finish(lifecycle, events, { reason, usage: state.usage })
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -884,4 +881,4 @@ export const route = Route.make({
|
|||||||
transport: httpTransport,
|
transport: httpTransport,
|
||||||
})
|
})
|
||||||
|
|
||||||
export * as OpenAIChat from "./openai-chat.js"
|
export * as OpenAIChat from "./openai-chat"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Route, type RouteRoutedLanguageModelInput } from "../route/client.js"
|
import { Route, type RouteRoutedLanguageModelInput } from "../route/client"
|
||||||
import { Endpoint } from "../route/endpoint.js"
|
import { Endpoint } from "../route/endpoint"
|
||||||
import { Framing } from "../route/framing.js"
|
import { Framing } from "../route/framing"
|
||||||
import * as OpenAIChat from "./openai-chat.js"
|
import * as OpenAIChat from "./openai-chat"
|
||||||
|
|
||||||
const ADAPTER = "openai-compatible-chat"
|
const ADAPTER = "openai-compatible-chat"
|
||||||
|
|
||||||
@@ -22,4 +22,4 @@ export const route = Route.make({
|
|||||||
framing: Framing.sse,
|
framing: Framing.sse,
|
||||||
})
|
})
|
||||||
|
|
||||||
export * as OpenAICompatibleChat from "./openai-compatible-chat.js"
|
export * as OpenAICompatibleChat from "./openai-compatible-chat"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Route, type RouteRoutedLanguageModelInput } from "../route/client.js"
|
import { Route, type RouteRoutedLanguageModelInput } from "../route/client"
|
||||||
import { Endpoint } from "../route/endpoint.js"
|
import { Endpoint } from "../route/endpoint"
|
||||||
import { OpenResponses } from "./open-responses.js"
|
import { OpenResponses } from "./open-responses"
|
||||||
|
|
||||||
const ADAPTER = "openai-compatible-responses"
|
const ADAPTER = "openai-compatible-responses"
|
||||||
|
|
||||||
@@ -19,4 +19,4 @@ export const route = Route.make({
|
|||||||
transport: OpenResponses.httpTransport,
|
transport: OpenResponses.httpTransport,
|
||||||
})
|
})
|
||||||
|
|
||||||
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js"
|
export * as OpenAICompatibleResponses from "./openai-compatible-responses"
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import {
|
|||||||
type ImageInput,
|
type ImageInput,
|
||||||
type ImageRequestFor,
|
type ImageRequestFor,
|
||||||
type ImageRoute,
|
type ImageRoute,
|
||||||
} from "../image.js"
|
} from "../image"
|
||||||
import { Auth, type Definition as AuthDefinition } from "../route/auth.js"
|
import { Auth, type Definition as AuthDefinition } from "../route/auth"
|
||||||
import {
|
import {
|
||||||
InvalidProviderOutputReason,
|
InvalidProviderOutputReason,
|
||||||
AIError,
|
AIError,
|
||||||
@@ -16,10 +16,10 @@ import {
|
|||||||
mergeHttpOptions,
|
mergeHttpOptions,
|
||||||
mergeJsonRecords,
|
mergeJsonRecords,
|
||||||
type HttpOptions,
|
type HttpOptions,
|
||||||
} from "../schema/index.js"
|
} from "../schema"
|
||||||
import { ProviderShared } from "./shared.js"
|
import { ProviderShared } from "./shared"
|
||||||
import { ImageInputs } from "./utils/image-input.js"
|
import { ImageInputs } from "./utils/image-input"
|
||||||
import { OpenAIImage } from "./utils/openai-image.js"
|
import { OpenAIImage } from "./utils/openai-image"
|
||||||
|
|
||||||
const ADAPTER = "openai-images"
|
const ADAPTER = "openai-images"
|
||||||
export const DEFAULT_BASE_URL = "https://api.openai.com/v1"
|
export const DEFAULT_BASE_URL = "https://api.openai.com/v1"
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Effect, Encoding, Schema } from "effect"
|
import { Effect, Encoding, Schema } from "effect"
|
||||||
import { Route } from "../route/client.js"
|
import { Route } from "../route/client"
|
||||||
import { Auth } from "../route/auth.js"
|
import { Auth } from "../route/auth"
|
||||||
import { Endpoint } from "../route/endpoint.js"
|
import { Endpoint } from "../route/endpoint"
|
||||||
import { Protocol } from "../route/protocol.js"
|
import { Protocol } from "../route/protocol"
|
||||||
import { HttpTransport, WebSocketTransport } from "../route/transport/index.js"
|
import { HttpTransport, WebSocketTransport } from "../route/transport"
|
||||||
import { LLMEvent, LLMRequest, type JsonSchema, type ToolDefinition } from "../schema/index.js"
|
import { LLMEvent, LLMRequest, type JsonSchema, type ToolDefinition } from "../schema"
|
||||||
import { OpenResponses } from "./open-responses.js"
|
import { OpenResponses } from "./open-responses"
|
||||||
import { optionalArray, ProviderShared } from "./shared.js"
|
import { optionalArray, ProviderShared } from "./shared"
|
||||||
import { Lifecycle } from "./utils/lifecycle.js"
|
import { Lifecycle } from "./utils/lifecycle"
|
||||||
import { OpenAIImage } from "./utils/openai-image.js"
|
import { OpenAIImage } from "./utils/openai-image"
|
||||||
import { ToolSchemaProjection } from "./utils/tool-schema.js"
|
import { ToolSchemaProjection } from "./utils/tool-schema"
|
||||||
|
|
||||||
const ADAPTER = "openai-responses"
|
const ADAPTER = "openai-responses"
|
||||||
const NAME = "OpenAI Responses"
|
const NAME = "OpenAI Responses"
|
||||||
@@ -290,4 +290,4 @@ export const webSocketRoute = Route.make({
|
|||||||
defaults: { providerOptions: { openai: { store: false } } },
|
defaults: { providerOptions: { openai: { store: false } } },
|
||||||
})
|
})
|
||||||
|
|
||||||
export * as OpenAIResponses from "./openai-responses.js"
|
export * as OpenAIResponses from "./openai-responses"
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import {
|
|||||||
type MediaPart,
|
type MediaPart,
|
||||||
type TextPart,
|
type TextPart,
|
||||||
type ToolResultPart,
|
type ToolResultPart,
|
||||||
} from "../schema/index.js"
|
} from "../schema"
|
||||||
import { isRecord } from "../utils/record.js"
|
import { isRecord } from "../utils/record"
|
||||||
export { isRecord }
|
export { isRecord }
|
||||||
|
|
||||||
export const Json = Schema.fromJsonString(Schema.Unknown)
|
export const Json = Schema.fromJsonString(Schema.Unknown)
|
||||||
@@ -324,4 +324,4 @@ export const jsonPost = (input: { readonly url: string; readonly body: string; r
|
|||||||
HttpClientRequest.bodyText(input.body, "application/json"),
|
HttpClientRequest.bodyText(input.body, "application/json"),
|
||||||
)
|
)
|
||||||
|
|
||||||
export * as ProviderShared from "./shared.js"
|
export * as ProviderShared from "./shared"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { AwsV4Signer } from "aws4fetch"
|
import { AwsV4Signer } from "aws4fetch"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { Headers } from "effect/unstable/http"
|
import { Headers } from "effect/unstable/http"
|
||||||
import { Auth, type AuthInput } from "../../route/auth.js"
|
import { Auth, type AuthInput } from "../../route/auth"
|
||||||
import { ProviderShared } from "../shared.js"
|
import { ProviderShared } from "../shared"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AWS credentials for SigV4 signing. Bedrock also supports Bearer API key auth,
|
* AWS credentials for SigV4 signing. Bedrock also supports Bearer API key auth,
|
||||||
@@ -74,4 +74,4 @@ export const sigV4 = (
|
|||||||
/** Bedrock route auth defaults to SigV4 and expects credentials from route configuration. */
|
/** Bedrock route auth defaults to SigV4 and expects credentials from route configuration. */
|
||||||
export const auth = sigV4(undefined)
|
export const auth = sigV4(undefined)
|
||||||
|
|
||||||
export * as BedrockAuth from "./bedrock-auth.js"
|
export * as BedrockAuth from "./bedrock-auth"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
import type { CacheHint } from "../../schema/index.js"
|
import type { CacheHint } from "../../schema"
|
||||||
import { newBreakpoints, ttlBucket, type Breakpoints } from "./cache.js"
|
import { newBreakpoints, ttlBucket, type Breakpoints } from "./cache"
|
||||||
|
|
||||||
// Bedrock cache markers are positional: emit a `cachePoint` block immediately
|
// Bedrock cache markers are positional: emit a `cachePoint` block immediately
|
||||||
// after the content the caller wants treated as a cacheable prefix. Bedrock
|
// after the content the caller wants treated as a cacheable prefix. Bedrock
|
||||||
@@ -18,7 +18,7 @@ export type CachePointBlock = Schema.Schema.Type<typeof CachePointBlock>
|
|||||||
// budget is respected across `system`, `messages`, and `tools`.
|
// budget is respected across `system`, `messages`, and `tools`.
|
||||||
export const BEDROCK_BREAKPOINT_CAP = 4
|
export const BEDROCK_BREAKPOINT_CAP = 4
|
||||||
|
|
||||||
export type { Breakpoints } from "./cache.js"
|
export type { Breakpoints } from "./cache"
|
||||||
export const breakpoints = () => newBreakpoints(BEDROCK_BREAKPOINT_CAP)
|
export const breakpoints = () => newBreakpoints(BEDROCK_BREAKPOINT_CAP)
|
||||||
|
|
||||||
const DEFAULT_5M: CachePointBlock = { cachePoint: { type: "default" } }
|
const DEFAULT_5M: CachePointBlock = { cachePoint: { type: "default" } }
|
||||||
@@ -34,4 +34,4 @@ export const block = (breakpoints: Breakpoints, cache: CacheHint | undefined): C
|
|||||||
return ttlBucket(cache.ttlSeconds) === "1h" ? DEFAULT_1H : DEFAULT_5M
|
return ttlBucket(cache.ttlSeconds) === "1h" ? DEFAULT_1H : DEFAULT_5M
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as BedrockCache from "./bedrock-cache.js"
|
export * as BedrockCache from "./bedrock-cache"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Effect, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import type { MediaPart } from "../../schema/index.js"
|
import type { MediaPart } from "../../schema"
|
||||||
import { ProviderShared } from "../shared.js"
|
import { ProviderShared } from "../shared"
|
||||||
|
|
||||||
// Bedrock Converse accepts image `format` as the file extension and
|
// Bedrock Converse accepts image `format` as the file extension and
|
||||||
// `source.bytes` as base64 in the JSON wire format.
|
// `source.bytes` as base64 in the JSON wire format.
|
||||||
@@ -89,4 +89,4 @@ export const lower = Effect.fn("BedrockMedia.lower")(function* (part: MediaPart)
|
|||||||
return yield* ProviderShared.invalidRequest(`Bedrock Converse does not support media type ${part.mediaType}`)
|
return yield* ProviderShared.invalidRequest(`Bedrock Converse does not support media type ${part.mediaType}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
export * as BedrockMedia from "./bedrock-media.js"
|
export * as BedrockMedia from "./bedrock-media"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { isRecord } from "../../utils/record.js"
|
import { isRecord } from "../../utils/record"
|
||||||
|
|
||||||
// Gemini accepts a JSON Schema-like dialect for tool parameters, but rejects a
|
// Gemini accepts a JSON Schema-like dialect for tool parameters, but rejects a
|
||||||
// handful of common JSON Schema shapes. Keep this projection isolated so the
|
// handful of common JSON Schema shapes. Keep this projection isolated so the
|
||||||
@@ -61,59 +61,39 @@ const emptyObjectSchema = (schema: Record<string, unknown>) =>
|
|||||||
(!isRecord(schema.properties) || Object.keys(schema.properties).length === 0) &&
|
(!isRecord(schema.properties) || Object.keys(schema.properties).length === 0) &&
|
||||||
!schema.additionalProperties
|
!schema.additionalProperties
|
||||||
|
|
||||||
const projectNode = (schema: unknown, nested = false): Record<string, unknown> | undefined => {
|
const projectNode = (schema: unknown): Record<string, unknown> | undefined => {
|
||||||
if (!isRecord(schema)) return undefined
|
if (!isRecord(schema)) return undefined
|
||||||
if (!nested && emptyObjectSchema(schema)) return undefined
|
if (emptyObjectSchema(schema)) return undefined
|
||||||
const types = Array.isArray(schema.type) ? schema.type.filter((type) => type !== "null") : undefined
|
return Object.fromEntries(
|
||||||
const anyOf = Array.isArray(schema.anyOf) ? schema.anyOf : undefined
|
|
||||||
const hasNullAnyOf = anyOf?.some((item) => isRecord(item) && item.type === "null") ?? false
|
|
||||||
const anyOfTypes = hasNullAnyOf ? anyOf?.filter((item) => !isRecord(item) || item.type !== "null") : anyOf
|
|
||||||
const flattenedAnyOf = hasNullAnyOf && anyOfTypes?.length === 1 ? projectNode(anyOfTypes[0], true) : undefined
|
|
||||||
const result = Object.fromEntries(
|
|
||||||
[
|
[
|
||||||
["description", schema.description],
|
["description", schema.description],
|
||||||
["required", schema.required],
|
["required", schema.required],
|
||||||
["format", schema.format],
|
["format", schema.format],
|
||||||
["type", types ? (types.length === 0 ? "null" : undefined) : schema.type],
|
["type", Array.isArray(schema.type) ? schema.type.filter((type) => type !== "null")[0] : schema.type],
|
||||||
[
|
["nullable", Array.isArray(schema.type) && schema.type.includes("null") ? true : undefined],
|
||||||
"nullable",
|
|
||||||
(Array.isArray(schema.type) && schema.type.includes("null") && types && types.length > 0) || hasNullAnyOf
|
|
||||||
? true
|
|
||||||
: undefined,
|
|
||||||
],
|
|
||||||
["enum", schema.const !== undefined ? [schema.const] : schema.enum],
|
["enum", schema.const !== undefined ? [schema.const] : schema.enum],
|
||||||
[
|
[
|
||||||
"properties",
|
"properties",
|
||||||
isRecord(schema.properties)
|
isRecord(schema.properties)
|
||||||
? Object.fromEntries(Object.entries(schema.properties).map(([key, value]) => [key, projectNode(value, true)]))
|
? Object.fromEntries(Object.entries(schema.properties).map(([key, value]) => [key, projectNode(value)]))
|
||||||
: undefined,
|
: undefined,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"items",
|
"items",
|
||||||
Array.isArray(schema.items)
|
Array.isArray(schema.items)
|
||||||
? schema.items.map((item) => projectNode(item, true))
|
? schema.items.map(projectNode)
|
||||||
: schema.items === undefined
|
: schema.items === undefined
|
||||||
? undefined
|
? undefined
|
||||||
: projectNode(schema.items, true),
|
: projectNode(schema.items),
|
||||||
],
|
],
|
||||||
["allOf", Array.isArray(schema.allOf) ? schema.allOf.map((item) => projectNode(item, true)) : undefined],
|
["allOf", Array.isArray(schema.allOf) ? schema.allOf.map(projectNode) : undefined],
|
||||||
[
|
["anyOf", Array.isArray(schema.anyOf) ? schema.anyOf.map(projectNode) : undefined],
|
||||||
"anyOf",
|
["oneOf", Array.isArray(schema.oneOf) ? schema.oneOf.map(projectNode) : undefined],
|
||||||
anyOfTypes
|
|
||||||
? hasNullAnyOf && anyOfTypes.length === 1
|
|
||||||
? undefined
|
|
||||||
: anyOfTypes.map((item) => projectNode(item, true))
|
|
||||||
: types && types.length > 0
|
|
||||||
? types.map((type) => ({ type }))
|
|
||||||
: undefined,
|
|
||||||
],
|
|
||||||
["oneOf", Array.isArray(schema.oneOf) ? schema.oneOf.map((item) => projectNode(item, true)) : undefined],
|
|
||||||
["minLength", schema.minLength],
|
["minLength", schema.minLength],
|
||||||
].filter((entry) => entry[1] !== undefined),
|
].filter((entry) => entry[1] !== undefined),
|
||||||
)
|
)
|
||||||
return flattenedAnyOf ? { ...result, ...flattenedAnyOf } : result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const convert = (schema: unknown) => projectNode(sanitizeNode(schema))
|
export const convert = (schema: unknown) => projectNode(sanitizeNode(schema))
|
||||||
|
|
||||||
export * as GeminiToolSchema from "./gemini-tool-schema.js"
|
export * as GeminiToolSchema from "./gemini-tool-schema"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Effect, Encoding } from "effect"
|
import { Effect, Encoding } from "effect"
|
||||||
import type { ImageInput } from "../../image.js"
|
import type { ImageInput } from "../../image"
|
||||||
import { InvalidRequestReason, AIError } from "../../schema/index.js"
|
import { InvalidRequestReason, AIError } from "../../schema"
|
||||||
|
|
||||||
const invalid = (module: string, message: string) =>
|
const invalid = (module: string, message: string) =>
|
||||||
new AIError({
|
new AIError({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { LLMEvent, type FinishReasonDetails, type ProviderMetadata, type Usage } from "../../schema/index.js"
|
import { LLMEvent, type FinishReasonDetails, type ProviderMetadata, type Usage } from "../../schema"
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
readonly stepStarted: boolean
|
readonly stepStarted: boolean
|
||||||
@@ -102,4 +102,4 @@ export const finish = (
|
|||||||
return { ...stepped, stepStarted: false }
|
return { ...stepped, stepStarted: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as Lifecycle from "./lifecycle.js"
|
export * as Lifecycle from "./lifecycle"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
import { TextVerbosity, type LLMRequest } from "../../schema/index.js"
|
import { TextVerbosity, type LLMRequest } from "../../schema"
|
||||||
|
|
||||||
export const ResponseIncludables = [
|
export const ResponseIncludables = [
|
||||||
"file_search_call.results",
|
"file_search_call.results",
|
||||||
@@ -33,6 +33,7 @@ export const ServiceTierSchema = Schema.Literals(ServiceTiers)
|
|||||||
export interface Resolved {
|
export interface Resolved {
|
||||||
readonly instructions?: string
|
readonly instructions?: string
|
||||||
readonly store?: boolean
|
readonly store?: boolean
|
||||||
|
readonly promptCacheKey?: string
|
||||||
readonly reasoningEffort?: string
|
readonly reasoningEffort?: string
|
||||||
readonly reasoningSummary?: "auto" | "concise" | "detailed"
|
readonly reasoningSummary?: "auto" | "concise" | "detailed"
|
||||||
readonly include?: ReadonlyArray<ResponseIncludable>
|
readonly include?: ReadonlyArray<ResponseIncludable>
|
||||||
@@ -49,6 +50,7 @@ export const resolve = (request: LLMRequest): Resolved => {
|
|||||||
return {
|
return {
|
||||||
instructions: typeof input?.instructions === "string" ? input.instructions : undefined,
|
instructions: typeof input?.instructions === "string" ? input.instructions : undefined,
|
||||||
store: typeof input?.store === "boolean" ? input.store : undefined,
|
store: typeof input?.store === "boolean" ? input.store : undefined,
|
||||||
|
promptCacheKey: typeof input?.promptCacheKey === "string" ? input.promptCacheKey : undefined,
|
||||||
reasoningEffort: typeof input?.reasoningEffort === "string" ? input.reasoningEffort : undefined,
|
reasoningEffort: typeof input?.reasoningEffort === "string" ? input.reasoningEffort : undefined,
|
||||||
reasoningSummary:
|
reasoningSummary:
|
||||||
reasoningSummary === "auto" || reasoningSummary === "concise" || reasoningSummary === "detailed"
|
reasoningSummary === "auto" || reasoningSummary === "concise" || reasoningSummary === "detailed"
|
||||||
@@ -60,4 +62,4 @@ export const resolve = (request: LLMRequest): Resolved => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as OpenResponsesOptions from "./open-responses-options.js"
|
export * as OpenResponsesOptions from "./open-responses-options"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ReasoningEfforts } from "../../schema/index.js"
|
import { ReasoningEfforts } from "../../schema"
|
||||||
import { OpenResponsesOptions } from "./open-responses-options.js"
|
import { OpenResponsesOptions } from "./open-responses-options"
|
||||||
|
|
||||||
export const OpenAIReasoningEfforts = ReasoningEfforts
|
export const OpenAIReasoningEfforts = ReasoningEfforts
|
||||||
export type OpenAIReasoningEffort = string
|
export type OpenAIReasoningEffort = string
|
||||||
@@ -20,4 +20,4 @@ export const isReasoningEffort = (effort: unknown): effort is OpenAIReasoningEff
|
|||||||
|
|
||||||
export const resolve = OpenResponsesOptions.resolve
|
export const resolve = OpenResponsesOptions.resolve
|
||||||
|
|
||||||
export * as OpenAIOptions from "./openai-options.js"
|
export * as OpenAIOptions from "./openai-options"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { JsonSchema, LanguageModelToolSchemaCompatibility } from "../../schema/index.js"
|
import type { JsonSchema, LanguageModelToolSchemaCompatibility } from "../../schema"
|
||||||
import { isRecord } from "../../utils/record.js"
|
import { isRecord } from "../../utils/record"
|
||||||
import { GeminiToolSchema } from "./gemini-tool-schema.js"
|
import { GeminiToolSchema } from "./gemini-tool-schema"
|
||||||
|
|
||||||
const removeNullSchemas = (value: unknown): unknown => {
|
const removeNullSchemas = (value: unknown): unknown => {
|
||||||
if (Array.isArray(value)) return value.map(removeNullSchemas)
|
if (Array.isArray(value)) return value.map(removeNullSchemas)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { AIError, LLMEvent, type ProviderMetadata, type ToolCall, type ToolInputError } from "../../schema/index.js"
|
import { AIError, LLMEvent, type ProviderMetadata, type ToolCall, type ToolInputError } from "../../schema"
|
||||||
import { eventError, parseToolInput, type ToolAccumulator } from "../shared.js"
|
import { eventError, parseToolInput, type ToolAccumulator } from "../shared"
|
||||||
|
|
||||||
type StreamKey = string | number
|
type StreamKey = string | number
|
||||||
|
|
||||||
@@ -223,4 +223,4 @@ export const finishAll = <K extends StreamKey>(route: string, tools: State<K>) =
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export * as ToolStream from "./tool-stream.js"
|
export * as ToolStream from "./tool-stream"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Effect, Encoding, Schema } from "effect"
|
import { Effect, Encoding, Schema } from "effect"
|
||||||
import { Headers, HttpClientRequest } from "effect/unstable/http"
|
import { Headers, HttpClientRequest } from "effect/unstable/http"
|
||||||
import { GeneratedImage, ImageModel, ImageResponse, type ImageRequestFor, type ImageRoute } from "../image.js"
|
import { GeneratedImage, ImageModel, ImageResponse, type ImageRequestFor, type ImageRoute } from "../image"
|
||||||
import { Auth, type Definition as AuthDefinition } from "../route/auth.js"
|
import { Auth, type Definition as AuthDefinition } from "../route/auth"
|
||||||
import {
|
import {
|
||||||
InvalidProviderOutputReason,
|
InvalidProviderOutputReason,
|
||||||
AIError,
|
AIError,
|
||||||
@@ -9,9 +9,9 @@ import {
|
|||||||
mergeHttpOptions,
|
mergeHttpOptions,
|
||||||
mergeJsonRecords,
|
mergeJsonRecords,
|
||||||
type HttpOptions,
|
type HttpOptions,
|
||||||
} from "../schema/index.js"
|
} from "../schema"
|
||||||
import { ProviderShared, optionalNull } from "./shared.js"
|
import { ProviderShared, optionalNull } from "./shared"
|
||||||
import { ImageInputs } from "./utils/image-input.js"
|
import { ImageInputs } from "./utils/image-input"
|
||||||
|
|
||||||
const ADAPTER = "xai-images"
|
const ADAPTER = "xai-images"
|
||||||
export const DEFAULT_BASE_URL = "https://api.x.ai/v1"
|
export const DEFAULT_BASE_URL = "https://api.x.ai/v1"
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
import { Effect, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { Headers, HttpClientRequest } from "effect/unstable/http"
|
import { Headers, HttpClientRequest } from "effect/unstable/http"
|
||||||
import { GeneratedImage, ImageModel, ImageResponse, type ImageRequestFor, type ImageRoute } from "../image.js"
|
import { GeneratedImage, ImageModel, ImageResponse, type ImageRequestFor, type ImageRoute } from "../image"
|
||||||
import { Auth, type Definition as AuthDefinition } from "../route/auth.js"
|
import { Auth, type Definition as AuthDefinition } from "../route/auth"
|
||||||
import {
|
import { InvalidProviderOutputReason, AIError, mergeHttpOptions, mergeJsonRecords, type HttpOptions } from "../schema"
|
||||||
InvalidProviderOutputReason,
|
import { ProviderShared } from "./shared"
|
||||||
AIError,
|
import { ImageInputs } from "./utils/image-input"
|
||||||
mergeHttpOptions,
|
|
||||||
mergeJsonRecords,
|
|
||||||
type HttpOptions,
|
|
||||||
} from "../schema/index.js"
|
|
||||||
import { ProviderShared } from "./shared.js"
|
|
||||||
import { ImageInputs } from "./utils/image-input.js"
|
|
||||||
|
|
||||||
const ADAPTER = "zai-images"
|
const ADAPTER = "zai-images"
|
||||||
export const DEFAULT_BASE_URL = "https://api.z.ai/api/paas/v4"
|
export const DEFAULT_BASE_URL = "https://api.z.ai/api/paas/v4"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
type HttpContext,
|
type HttpContext,
|
||||||
type HttpRateLimitDetails,
|
type HttpRateLimitDetails,
|
||||||
type ProviderMetadata,
|
type ProviderMetadata,
|
||||||
} from "./schema/index.js"
|
} from "./schema"
|
||||||
|
|
||||||
const patterns = [
|
const patterns = [
|
||||||
/prompt is too long/i,
|
/prompt is too long/i,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { LanguageModel, ProviderOptions } from "./schema/index.js"
|
import type { LanguageModel, ProviderOptions } from "./schema"
|
||||||
|
|
||||||
export interface Settings extends Readonly<Record<string, unknown>> {
|
export interface Settings extends Readonly<Record<string, unknown>> {
|
||||||
readonly baseURL?: string
|
readonly baseURL?: string
|
||||||
@@ -18,4 +18,4 @@ export interface Definition<
|
|||||||
readonly model: (modelID: string, settings: ProviderSettings) => LanguageModel<Options>
|
readonly model: (modelID: string, settings: ProviderSettings) => LanguageModel<Options>
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as ProviderPackage from "./provider-package.js"
|
export * as ProviderPackage from "./provider-package"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { LanguageModel, ModelID, ProviderID } from "./schema/index.js"
|
import type { LanguageModel, ModelID, ProviderID } from "./schema"
|
||||||
|
|
||||||
export type LanguageModelOptions = Pick<LanguageModel.Input, "defaults" | "compatibility">
|
export type LanguageModelOptions = Pick<LanguageModel.Input, "defaults" | "compatibility">
|
||||||
|
|
||||||
@@ -33,4 +33,4 @@ export const make = <DefinitionType extends DefinitionShape>(
|
|||||||
definition: NoExtraFields<DefinitionType, DefinitionShape>,
|
definition: NoExtraFields<DefinitionType, DefinitionShape>,
|
||||||
) => definition
|
) => definition
|
||||||
|
|
||||||
export * as Provider from "./provider.js"
|
export * as Provider from "./provider"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from "./providers/index.js"
|
export * from "./providers/index"
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Auth } from "../route/auth.js"
|
import { Auth } from "../route/auth"
|
||||||
import type { Route as RouteDef, RouteDefaultsInput } from "../route/client.js"
|
import type { Route as RouteDef, RouteDefaultsInput } from "../route/client"
|
||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { OpenAIChat } from "../protocols/openai-chat.js"
|
import { OpenAIChat } from "../protocols/openai-chat"
|
||||||
import { OpenAIResponses } from "../protocols/openai-responses.js"
|
import { OpenAIResponses } from "../protocols/openai-responses"
|
||||||
import { BedrockAuth, type Credentials } from "../protocols/utils/bedrock-auth.js"
|
import { BedrockAuth, type Credentials } from "../protocols/utils/bedrock-auth"
|
||||||
import { ProviderID, type ModelID } from "../schema/index.js"
|
import { ProviderID, type ModelID } from "../schema"
|
||||||
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options.js"
|
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options"
|
||||||
|
|
||||||
export const id = ProviderID.make("amazon-bedrock")
|
export const id = ProviderID.make("amazon-bedrock")
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { RouteDefaultsInput } from "../route/client.js"
|
import type { RouteDefaultsInput } from "../route/client"
|
||||||
import { Auth } from "../route/auth.js"
|
import { Auth } from "../route/auth"
|
||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { ProviderID, type ModelID } from "../schema/index.js"
|
import { ProviderID, type ModelID } from "../schema"
|
||||||
import * as BedrockConverse from "../protocols/bedrock-converse.js"
|
import * as BedrockConverse from "../protocols/bedrock-converse"
|
||||||
import type { BedrockCredentials } from "../protocols/bedrock-converse.js"
|
import type { BedrockCredentials } from "../protocols/bedrock-converse"
|
||||||
|
|
||||||
export const id = ProviderID.make("amazon-bedrock")
|
export const id = ProviderID.make("amazon-bedrock")
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { chatModel as model } from "../amazon-bedrock-mantle.js"
|
export { chatModel as model } from "../amazon-bedrock-mantle"
|
||||||
export type { Settings } from "../amazon-bedrock-mantle.js"
|
export type { Settings } from "../amazon-bedrock-mantle"
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { chatModel as model } from "../../amazon-bedrock-mantle.js"
|
export { chatModel as model } from "../../amazon-bedrock-mantle"
|
||||||
export type { Settings } from "../../amazon-bedrock-mantle.js"
|
export type { Settings } from "../../amazon-bedrock-mantle"
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { responsesModel as model } from "../../amazon-bedrock-mantle.js"
|
export { responsesModel as model } from "../../amazon-bedrock-mantle"
|
||||||
export type { Settings } from "../../amazon-bedrock-mantle.js"
|
export type { Settings } from "../../amazon-bedrock-mantle"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { AnthropicMessages } from "../protocols/anthropic-messages.js"
|
import { AnthropicMessages } from "../protocols/anthropic-messages"
|
||||||
import { Auth } from "../route/auth.js"
|
import { Auth } from "../route/auth"
|
||||||
import type { ProviderAuthOption } from "../route/auth-options.js"
|
import type { ProviderAuthOption } from "../route/auth-options"
|
||||||
import type { RouteDefaultsInput } from "../route/client.js"
|
import type { RouteDefaultsInput } from "../route/client"
|
||||||
import { ProviderID, type ModelID } from "../schema/index.js"
|
import { ProviderID, type ModelID } from "../schema"
|
||||||
|
|
||||||
export type AnthropicOptionsInput = AnthropicMessages.OptionsInput
|
export type AnthropicOptionsInput = AnthropicMessages.OptionsInput
|
||||||
export type AnthropicProviderOptionsInput = AnthropicMessages.ProviderOptionsInput
|
export type AnthropicProviderOptionsInput = AnthropicMessages.ProviderOptionsInput
|
||||||
@@ -74,4 +74,4 @@ export const model: ProviderPackage.Definition<Settings, AnthropicMessages.Provi
|
|||||||
}).model(modelID)
|
}).model(modelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as AnthropicCompatible from "./anthropic-compatible.js"
|
export * as AnthropicCompatible from "./anthropic-compatible"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { RouteDefaultsInput } from "../route/client.js"
|
import type { RouteDefaultsInput } from "../route/client"
|
||||||
import { Auth } from "../route/auth.js"
|
import { Auth } from "../route/auth"
|
||||||
import type { ProviderAuthOption } from "../route/auth-options.js"
|
import type { ProviderAuthOption } from "../route/auth-options"
|
||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { ProviderID, type ModelID } from "../schema/index.js"
|
import { ProviderID, type ModelID } from "../schema"
|
||||||
import { AnthropicMessages } from "../protocols/anthropic-messages.js"
|
import { AnthropicMessages } from "../protocols/anthropic-messages"
|
||||||
import { AnthropicCompatible } from "./anthropic-compatible.js"
|
import { AnthropicCompatible } from "./anthropic-compatible"
|
||||||
|
|
||||||
export type AnthropicOptionsInput = AnthropicMessages.OptionsInput
|
export type AnthropicOptionsInput = AnthropicMessages.OptionsInput
|
||||||
export type AnthropicProviderOptionsInput = AnthropicMessages.ProviderOptionsInput
|
export type AnthropicProviderOptionsInput = AnthropicMessages.ProviderOptionsInput
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Auth } from "../route/auth.js"
|
import { Auth } from "../route/auth"
|
||||||
import { type AtLeastOne, type ProviderAuthOption } from "../route/auth-options.js"
|
import { type AtLeastOne, type ProviderAuthOption } from "../route/auth-options"
|
||||||
import type { Route as RouteDef, RouteDefaultsInput } from "../route/client.js"
|
import type { Route as RouteDef, RouteDefaultsInput } from "../route/client"
|
||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { ProviderID, type ModelID } from "../schema/index.js"
|
import { ProviderID, type ModelID } from "../schema"
|
||||||
import * as OpenAIChat from "../protocols/openai-chat.js"
|
import * as OpenAIChat from "../protocols/openai-chat"
|
||||||
import * as OpenAIResponses from "../protocols/openai-responses.js"
|
import * as OpenAIResponses from "../protocols/openai-responses"
|
||||||
import { ProviderShared } from "../protocols/shared.js"
|
import { ProviderShared } from "../protocols/shared"
|
||||||
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options.js"
|
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options"
|
||||||
|
|
||||||
export const id = ProviderID.make("azure")
|
export const id = ProviderID.make("azure")
|
||||||
const routeAuth = Auth.remove("authorization")
|
const routeAuth = Auth.remove("authorization")
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { chatModel as model } from "../azure.js"
|
export { chatModel as model } from "../azure"
|
||||||
export type { Settings } from "../azure.js"
|
export type { Settings } from "../azure"
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { responsesModel as model } from "../azure.js"
|
export { responsesModel as model } from "../azure"
|
||||||
export type { Settings } from "../azure.js"
|
export type { Settings } from "../azure"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { Config, Redacted } from "effect"
|
import type { Config, Redacted } from "effect"
|
||||||
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat.js"
|
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat"
|
||||||
import { Auth } from "../route/auth.js"
|
import { Auth } from "../route/auth"
|
||||||
import { AuthOptions, type AtLeastOne, type ProviderAuthOption } from "../route/auth-options.js"
|
import { AuthOptions, type AtLeastOne, type ProviderAuthOption } from "../route/auth-options"
|
||||||
import type { RouteDefaultsInput } from "../route/client.js"
|
import type { RouteDefaultsInput } from "../route/client"
|
||||||
import { ProviderID, type ModelID } from "../schema/index.js"
|
import { ProviderID, type ModelID } from "../schema"
|
||||||
import type { OpenAIProviderOptionsInput } from "./openai-options.js"
|
import type { OpenAIProviderOptionsInput } from "./openai-options"
|
||||||
|
|
||||||
export const aiGatewayID = ProviderID.make("cloudflare-ai-gateway")
|
export const aiGatewayID = ProviderID.make("cloudflare-ai-gateway")
|
||||||
export const workersAIID = ProviderID.make("cloudflare-workers-ai")
|
export const workersAIID = ProviderID.make("cloudflare-workers-ai")
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
|
||||||
|
import type { RouteDefaultsInput } from "../route/client"
|
||||||
|
import { ProviderID, type ModelID } from "../schema"
|
||||||
|
import * as OpenAIChat from "../protocols/openai-chat"
|
||||||
|
import * as OpenAIResponses from "../protocols/openai-responses"
|
||||||
|
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options"
|
||||||
|
|
||||||
|
export const id = ProviderID.make("github-copilot")
|
||||||
|
|
||||||
|
// GitHub Copilot has no canonical public URL — callers (opencode, etc.) must
|
||||||
|
// supply `baseURL` explicitly.
|
||||||
|
export type LanguageModelOptions = Omit<RouteDefaultsInput, "providerOptions"> &
|
||||||
|
ProviderAuthOption<"optional"> & {
|
||||||
|
readonly baseURL: string
|
||||||
|
readonly endpoint?: "chat" | "responses"
|
||||||
|
readonly providerOptions?: OpenAIProviderOptionsInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export const shouldUseResponsesApi = (modelID: string | ModelID, endpoint?: LanguageModelOptions["endpoint"]) => {
|
||||||
|
if (endpoint) return endpoint === "responses"
|
||||||
|
const model = String(modelID)
|
||||||
|
const match = /^gpt-(\d+)/.exec(model)
|
||||||
|
if (!match) return false
|
||||||
|
return Number(match[1]) >= 5 && !model.startsWith("gpt-5-mini")
|
||||||
|
}
|
||||||
|
|
||||||
|
export const routes = [OpenAIResponses.route, OpenAIChat.route]
|
||||||
|
|
||||||
|
const chatRoute = OpenAIChat.route.with({ provider: id })
|
||||||
|
const responsesRoute = OpenAIResponses.route.with({ provider: id })
|
||||||
|
|
||||||
|
const defaults = (options: LanguageModelOptions) => {
|
||||||
|
const { apiKey: _, auth: _auth, baseURL: _baseURL, endpoint: _endpoint, ...rest } = options
|
||||||
|
return rest
|
||||||
|
}
|
||||||
|
|
||||||
|
const configuredResponsesRoute = (options: LanguageModelOptions) =>
|
||||||
|
responsesRoute.with({
|
||||||
|
endpoint: { baseURL: options.baseURL },
|
||||||
|
auth: AuthOptions.bearer(options, []),
|
||||||
|
})
|
||||||
|
|
||||||
|
const configuredChatRoute = (options: LanguageModelOptions) =>
|
||||||
|
chatRoute.with({
|
||||||
|
endpoint: { baseURL: options.baseURL },
|
||||||
|
auth: AuthOptions.bearer(options, []),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const configure = (options: LanguageModelOptions) => {
|
||||||
|
const responsesRoute = configuredResponsesRoute(options)
|
||||||
|
const chatRoute = configuredChatRoute(options)
|
||||||
|
const responses = (modelID: string | ModelID) =>
|
||||||
|
responsesRoute
|
||||||
|
.with(withOpenAIOptions(modelID, defaults(options)))
|
||||||
|
.model<OpenAIProviderOptionsInput>({ id: modelID })
|
||||||
|
const chat = (modelID: string | ModelID) =>
|
||||||
|
chatRoute.with(withOpenAIOptions(modelID, defaults(options))).model<OpenAIProviderOptionsInput>({ id: modelID })
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
model: (modelID: string | ModelID) =>
|
||||||
|
shouldUseResponsesApi(modelID, options.endpoint) ? responses(modelID) : chat(modelID),
|
||||||
|
responses,
|
||||||
|
chat,
|
||||||
|
configure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const provider = {
|
||||||
|
id,
|
||||||
|
configure,
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { OpenAICompatibleChat } from "../protocols/openai-compatible-chat.js"
|
import { OpenAICompatibleChat } from "../protocols/openai-compatible-chat"
|
||||||
import type { RouteDefaultsInput } from "../route/client.js"
|
import type { RouteDefaultsInput } from "../route/client"
|
||||||
import { ProviderID, type ModelID } from "../schema/index.js"
|
import { ProviderID, type ModelID } from "../schema"
|
||||||
import { GoogleVertexShared } from "./google-vertex-shared.js"
|
import { GoogleVertexShared } from "./google-vertex-shared"
|
||||||
import type { OpenAIProviderOptionsInput } from "./openai-options.js"
|
import type { OpenAIProviderOptionsInput } from "./openai-options"
|
||||||
|
|
||||||
export const id = ProviderID.make("google-vertex")
|
export const id = ProviderID.make("google-vertex")
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Effect, Schema, Struct } from "effect"
|
import { Effect, Schema, Struct } from "effect"
|
||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { AnthropicMessages } from "../protocols/anthropic-messages.js"
|
import { AnthropicMessages } from "../protocols/anthropic-messages"
|
||||||
import { Auth } from "../route/auth.js"
|
import { Auth } from "../route/auth"
|
||||||
import { Route, type RouteDefaultsInput } from "../route/client.js"
|
import { Route, type RouteDefaultsInput } from "../route/client"
|
||||||
import { Endpoint } from "../route/endpoint.js"
|
import { Endpoint } from "../route/endpoint"
|
||||||
import { Framing } from "../route/framing.js"
|
import { Framing } from "../route/framing"
|
||||||
import { Protocol } from "../route/protocol.js"
|
import { Protocol } from "../route/protocol"
|
||||||
import { ProviderID, type ModelID } from "../schema/index.js"
|
import { ProviderID, type ModelID } from "../schema"
|
||||||
import { GoogleVertexShared } from "./google-vertex-shared.js"
|
import { GoogleVertexShared } from "./google-vertex-shared"
|
||||||
|
|
||||||
export type AnthropicOptionsInput = AnthropicMessages.OptionsInput
|
export type AnthropicOptionsInput = AnthropicMessages.OptionsInput
|
||||||
export type AnthropicProviderOptionsInput = AnthropicMessages.ProviderOptionsInput
|
export type AnthropicProviderOptionsInput = AnthropicMessages.ProviderOptionsInput
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { OpenAICompatibleResponses } from "../protocols/openai-compatible-responses.js"
|
import { OpenAICompatibleResponses } from "../protocols/openai-compatible-responses"
|
||||||
import type { RouteDefaultsInput } from "../route/client.js"
|
import type { RouteDefaultsInput } from "../route/client"
|
||||||
import { ProviderID, type ModelID } from "../schema/index.js"
|
import { ProviderID, type ModelID } from "../schema"
|
||||||
import { GoogleVertexShared } from "./google-vertex-shared.js"
|
import { GoogleVertexShared } from "./google-vertex-shared"
|
||||||
import type { OpenResponsesProviderOptionsInput } from "./open-responses-options.js"
|
import type { OpenResponsesProviderOptionsInput } from "./open-responses-options"
|
||||||
|
|
||||||
export const id = ProviderID.make("google-vertex")
|
export const id = ProviderID.make("google-vertex")
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { AnyAuthClient } from "google-auth-library"
|
import type { AnyAuthClient } from "google-auth-library"
|
||||||
import { Effect, Redacted } from "effect"
|
import { Effect, Redacted } from "effect"
|
||||||
import { Auth, MissingCredentialError } from "../route/auth.js"
|
import { Auth, MissingCredentialError } from "../route/auth"
|
||||||
|
|
||||||
const SCOPE = "https://www.googleapis.com/auth/cloud-platform"
|
const SCOPE = "https://www.googleapis.com/auth/cloud-platform"
|
||||||
|
|
||||||
@@ -74,4 +74,4 @@ export const oauth = (input: OAuthOptions, project?: string) => {
|
|||||||
return adc(project)
|
return adc(project)
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as GoogleVertexShared from "./google-vertex-shared.js"
|
export * as GoogleVertexShared from "./google-vertex-shared"
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { Gemini } from "../protocols/gemini.js"
|
import { Gemini } from "../protocols/gemini"
|
||||||
import { Auth } from "../route/auth.js"
|
import { Auth } from "../route/auth"
|
||||||
import { Route, type RouteDefaultsInput } from "../route/client.js"
|
import { Route, type RouteDefaultsInput } from "../route/client"
|
||||||
import { Endpoint } from "../route/endpoint.js"
|
import { Endpoint } from "../route/endpoint"
|
||||||
import { Framing } from "../route/framing.js"
|
import { Framing } from "../route/framing"
|
||||||
import { ProviderID, type ModelID } from "../schema/index.js"
|
import { ProviderID, type ModelID } from "../schema"
|
||||||
import { GoogleVertexShared } from "./google-vertex-shared.js"
|
import { GoogleVertexShared } from "./google-vertex-shared"
|
||||||
|
|
||||||
export type GeminiOptionsInput = Gemini.OptionsInput
|
export type GeminiOptionsInput = Gemini.OptionsInput
|
||||||
export type GeminiProviderOptionsInput = Gemini.ProviderOptionsInput
|
export type GeminiProviderOptionsInput = Gemini.ProviderOptionsInput
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { model } from "../google-vertex-chat.js"
|
export { model } from "../google-vertex-chat"
|
||||||
export type { Settings } from "../google-vertex-chat.js"
|
export type { Settings } from "../google-vertex-chat"
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { model } from "../google-vertex.js"
|
export { model } from "../google-vertex"
|
||||||
export type { Settings } from "../google-vertex.js"
|
export type { Settings } from "../google-vertex"
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { model } from "../google-vertex-messages.js"
|
export { model } from "../google-vertex-messages"
|
||||||
export type { Settings } from "../google-vertex-messages.js"
|
export type { Settings } from "../google-vertex-messages"
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { model } from "../google-vertex-responses.js"
|
export { model } from "../google-vertex-responses"
|
||||||
export type { Settings } from "../google-vertex-responses.js"
|
export type { Settings } from "../google-vertex-responses"
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import type { RouteDefaultsInput } from "../route/client.js"
|
import type { RouteDefaultsInput } from "../route/client"
|
||||||
import { Auth } from "../route/auth.js"
|
import { Auth } from "../route/auth"
|
||||||
import type { ProviderAuthOption } from "../route/auth-options.js"
|
import type { ProviderAuthOption } from "../route/auth-options"
|
||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { HttpOptions, ProviderID, mergeHttpOptions, type ModelID } from "../schema/index.js"
|
import { HttpOptions, ProviderID, mergeHttpOptions, type ModelID } from "../schema"
|
||||||
import { Gemini } from "../protocols/gemini.js"
|
import { Gemini } from "../protocols/gemini"
|
||||||
import { GoogleImages } from "../protocols/google-images.js"
|
import { GoogleImages } from "../protocols/google-images"
|
||||||
|
|
||||||
export type { GoogleImageOptions } from "../protocols/google-images.js"
|
export type { GoogleImageOptions } from "../protocols/google-images"
|
||||||
export type GeminiOptionsInput = Gemini.OptionsInput
|
export type GeminiOptionsInput = Gemini.OptionsInput
|
||||||
export type GeminiProviderOptionsInput = Gemini.ProviderOptionsInput
|
export type GeminiProviderOptionsInput = Gemini.ProviderOptionsInput
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
export * as Anthropic from "./anthropic.js"
|
export * as Anthropic from "./anthropic"
|
||||||
export * as AnthropicCompatible from "./anthropic-compatible.js"
|
export * as AnthropicCompatible from "./anthropic-compatible"
|
||||||
export * as AmazonBedrock from "./amazon-bedrock.js"
|
export * as AmazonBedrock from "./amazon-bedrock"
|
||||||
export * as AmazonBedrockMantle from "./amazon-bedrock-mantle.js"
|
export * as AmazonBedrockMantle from "./amazon-bedrock-mantle"
|
||||||
export * as Azure from "./azure.js"
|
export * as Azure from "./azure"
|
||||||
export * as Cloudflare from "./cloudflare.js"
|
export * as Cloudflare from "./cloudflare"
|
||||||
export { CloudflareAIGateway, CloudflareWorkersAI } from "./cloudflare.js"
|
export { CloudflareAIGateway, CloudflareWorkersAI } from "./cloudflare"
|
||||||
export * as Google from "./google.js"
|
export * as GitHubCopilot from "./github-copilot"
|
||||||
export * as GoogleVertex from "./google-vertex.js"
|
export * as Google from "./google"
|
||||||
export * as GoogleVertexChat from "./google-vertex-chat.js"
|
export * as GoogleVertex from "./google-vertex"
|
||||||
export * as GoogleVertexMessages from "./google-vertex-messages.js"
|
export * as GoogleVertexChat from "./google-vertex-chat"
|
||||||
export * as GoogleVertexResponses from "./google-vertex-responses.js"
|
export * as GoogleVertexMessages from "./google-vertex-messages"
|
||||||
export * as OpenAI from "./openai.js"
|
export * as GoogleVertexResponses from "./google-vertex-responses"
|
||||||
export * as OpenAICompatible from "./openai-compatible.js"
|
export * as OpenAI from "./openai"
|
||||||
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js"
|
export * as OpenAICompatible from "./openai-compatible"
|
||||||
export * as OpenRouter from "./openrouter.js"
|
export * as OpenAICompatibleResponses from "./openai-compatible-responses"
|
||||||
export * as XAI from "./xai.js"
|
export * as OpenRouter from "./openrouter"
|
||||||
export * as ZAI from "./zai.js"
|
export * as XAI from "./xai"
|
||||||
|
export * as ZAI from "./zai"
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import type { ResponseIncludable, ServiceTier } from "../protocols/utils/open-responses-options.js"
|
import type { ResponseIncludable, ServiceTier } from "../protocols/utils/open-responses-options"
|
||||||
import type { ProviderOptions, ReasoningEffort, TextVerbosity } from "../schema/index.js"
|
import type { ProviderOptions, ReasoningEffort, TextVerbosity } from "../schema"
|
||||||
|
|
||||||
export interface OpenResponsesOptionsInput {
|
export interface OpenResponsesOptionsInput {
|
||||||
readonly [key: string]: unknown
|
readonly [key: string]: unknown
|
||||||
readonly instructions?: string
|
readonly instructions?: string
|
||||||
readonly store?: boolean
|
readonly store?: boolean
|
||||||
|
readonly promptCacheKey?: string
|
||||||
readonly reasoningEffort?: ReasoningEffort
|
readonly reasoningEffort?: ReasoningEffort
|
||||||
readonly reasoningSummary?: "auto" | "concise" | "detailed"
|
readonly reasoningSummary?: "auto" | "concise" | "detailed"
|
||||||
readonly include?: ReadonlyArray<ResponseIncludable>
|
readonly include?: ReadonlyArray<ResponseIncludable>
|
||||||
@@ -16,4 +17,4 @@ export type OpenResponsesProviderOptionsInput = ProviderOptions & {
|
|||||||
readonly openresponses?: OpenResponsesOptionsInput
|
readonly openresponses?: OpenResponsesOptionsInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as OpenResponsesProviderOptions from "./open-responses-options.js"
|
export * as OpenResponsesProviderOptions from "./open-responses-options"
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { OpenAICompatibleResponses } from "../protocols/openai-compatible-responses.js"
|
import { OpenAICompatibleResponses } from "../protocols/openai-compatible-responses"
|
||||||
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options.js"
|
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
|
||||||
import type { RouteDefaultsInput } from "../route/client.js"
|
import type { RouteDefaultsInput } from "../route/client"
|
||||||
import { ProviderID, type ModelID } from "../schema/index.js"
|
import { ProviderID, type ModelID } from "../schema"
|
||||||
import type { OpenResponsesProviderOptionsInput } from "./open-responses-options.js"
|
import type { OpenResponsesProviderOptionsInput } from "./open-responses-options"
|
||||||
|
|
||||||
export type { OpenResponsesOptionsInput, OpenResponsesProviderOptionsInput } from "./open-responses-options.js"
|
export type { OpenResponsesOptionsInput, OpenResponsesProviderOptionsInput } from "./open-responses-options"
|
||||||
|
|
||||||
export const id = ProviderID.make("openai-compatible")
|
export const id = ProviderID.make("openai-compatible")
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { ProviderID, type ModelID } from "../schema/index.js"
|
import { ProviderID, type ModelID } from "../schema"
|
||||||
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat.js"
|
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat"
|
||||||
import type { RouteDefaultsInput } from "../route/client.js"
|
import type { RouteDefaultsInput } from "../route/client"
|
||||||
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options.js"
|
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
|
||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { profiles, type OpenAICompatibleProfile } from "./openai-compatible-profile.js"
|
import { profiles, type OpenAICompatibleProfile } from "./openai-compatible-profile"
|
||||||
import type { OpenAIProviderOptionsInput } from "./openai-options.js"
|
import type { OpenAIProviderOptionsInput } from "./openai-options"
|
||||||
|
|
||||||
export const id = ProviderID.make("openai-compatible")
|
export const id = ProviderID.make("openai-compatible")
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from "../openai-compatible-responses.js"
|
export * from "../openai-compatible-responses"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { ProviderOptions } from "../schema/index.js"
|
import type { ProviderOptions } from "../schema"
|
||||||
import { mergeProviderOptions } from "../schema/index.js"
|
import { mergeProviderOptions } from "../schema"
|
||||||
import type { OpenResponsesOptionsInput } from "./open-responses-options.js"
|
import type { OpenResponsesOptionsInput } from "./open-responses-options"
|
||||||
|
|
||||||
export type { OpenAIResponseIncludable, OpenAIServiceTier } from "../protocols/utils/openai-options.js"
|
export type { OpenAIResponseIncludable, OpenAIServiceTier } from "../protocols/utils/openai-options"
|
||||||
|
|
||||||
export type OpenAIOptionsInput = OpenResponsesOptionsInput
|
export type OpenAIOptionsInput = OpenResponsesOptionsInput
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ const openAIProviderOptions = (options: OpenAIOptionsInput | undefined): Provide
|
|||||||
const openai = Object.fromEntries(
|
const openai = Object.fromEntries(
|
||||||
definedEntries({
|
definedEntries({
|
||||||
store: options?.store,
|
store: options?.store,
|
||||||
|
promptCacheKey: options?.promptCacheKey,
|
||||||
reasoningEffort: options?.reasoningEffort,
|
reasoningEffort: options?.reasoningEffort,
|
||||||
reasoningSummary: options?.reasoningSummary,
|
reasoningSummary: options?.reasoningSummary,
|
||||||
include: options?.include,
|
include: options?.include,
|
||||||
@@ -67,4 +68,4 @@ export const withOpenAIOptions = <Options extends { readonly providerOptions?: O
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as OpenAIProviderOptions from "./openai-options.js"
|
export * as OpenAIProviderOptions from "./openai-options"
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options.js"
|
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
|
||||||
import type { Route, RouteDefaultsInput } from "../route/client.js"
|
import type { Route, RouteDefaultsInput } from "../route/client"
|
||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import { HttpOptions, ProviderID, ToolDefinition, mergeHttpOptions, type ModelID } from "../schema/index.js"
|
import { HttpOptions, ProviderID, ToolDefinition, mergeHttpOptions, type ModelID } from "../schema"
|
||||||
import * as OpenAIChat from "../protocols/openai-chat.js"
|
import * as OpenAIChat from "../protocols/openai-chat"
|
||||||
import * as OpenAIResponses from "../protocols/openai-responses.js"
|
import * as OpenAIResponses from "../protocols/openai-responses"
|
||||||
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options.js"
|
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options"
|
||||||
import { OpenAIImages, type OpenAIImageString } from "../protocols/openai-images.js"
|
import { OpenAIImages, type OpenAIImageString } from "../protocols/openai-images"
|
||||||
|
|
||||||
export type { OpenAIOptionsInput, OpenAIResponseIncludable } from "./openai-options.js"
|
export type { OpenAIOptionsInput, OpenAIResponseIncludable } from "./openai-options"
|
||||||
export type { OpenAIImageOptions } from "../protocols/openai-images.js"
|
export type { OpenAIImageOptions } from "../protocols/openai-images"
|
||||||
|
|
||||||
export const id = ProviderID.make("openai")
|
export const id = ProviderID.make("openai")
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { chatModel as model } from "../openai.js"
|
export { chatModel as model } from "../openai"
|
||||||
export type { Settings } from "../openai.js"
|
export type { Settings } from "../openai"
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { model } from "../openai.js"
|
export { model } from "../openai"
|
||||||
export type { Settings } from "../openai.js"
|
export type { Settings } from "../openai"
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Effect, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { Route, type RouteDefaultsInput } from "../route/client.js"
|
import { Route, type RouteDefaultsInput } from "../route/client"
|
||||||
import { Endpoint } from "../route/endpoint.js"
|
import { Endpoint } from "../route/endpoint"
|
||||||
import { Framing } from "../route/framing.js"
|
import { Framing } from "../route/framing"
|
||||||
import { Protocol } from "../route/protocol.js"
|
import { Protocol } from "../route/protocol"
|
||||||
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options.js"
|
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
|
||||||
import { ProviderID, type CacheHint, type ModelID, type ProviderOptions } from "../schema/index.js"
|
import { ProviderID, type CacheHint, type ModelID, type ProviderOptions } from "../schema"
|
||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
import * as OpenAICompatibleProfiles from "./openai-compatible-profile.js"
|
import * as OpenAICompatibleProfiles from "./openai-compatible-profile"
|
||||||
import * as OpenAIChat from "../protocols/openai-chat.js"
|
import * as OpenAIChat from "../protocols/openai-chat"
|
||||||
import { newBreakpoints, ttlBucket } from "../protocols/utils/cache.js"
|
import { newBreakpoints, ttlBucket } from "../protocols/utils/cache"
|
||||||
import { isRecord } from "../protocols/shared.js"
|
import { isRecord } from "../protocols/shared"
|
||||||
|
|
||||||
export const profile = OpenAICompatibleProfiles.profiles.openrouter
|
export const profile = OpenAICompatibleProfiles.profiles.openrouter
|
||||||
export const id = ProviderID.make(profile.provider)
|
export const id = ProviderID.make(profile.provider)
|
||||||
@@ -55,6 +55,7 @@ export interface OpenRouterOptions {
|
|||||||
readonly debug?: Readonly<{ echo_upstream_body?: boolean }>
|
readonly debug?: Readonly<{ echo_upstream_body?: boolean }>
|
||||||
readonly models?: ReadonlyArray<string>
|
readonly models?: ReadonlyArray<string>
|
||||||
readonly plugins?: ReadonlyArray<OpenRouterPlugin>
|
readonly plugins?: ReadonlyArray<OpenRouterPlugin>
|
||||||
|
readonly promptCacheKey?: string
|
||||||
readonly provider?: OpenRouterProviderRouting
|
readonly provider?: OpenRouterProviderRouting
|
||||||
readonly reasoning?: Readonly<{
|
readonly reasoning?: Readonly<{
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
@@ -121,7 +122,6 @@ export const protocol = Protocol.make({
|
|||||||
...body,
|
...body,
|
||||||
messages,
|
messages,
|
||||||
...bodyOptions(request.providerOptions?.openrouter),
|
...bodyOptions(request.providerOptions?.openrouter),
|
||||||
...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
|
|
||||||
} as OpenRouterBody
|
} as OpenRouterBody
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -161,6 +161,7 @@ const bodyOptions = (input: unknown) => {
|
|||||||
...(isRecord(debug) ? { debug } : {}),
|
...(isRecord(debug) ? { debug } : {}),
|
||||||
...(typeof user === "string" ? { user } : {}),
|
...(typeof user === "string" ? { user } : {}),
|
||||||
...(isRecord(reasoning) ? { reasoning } : {}),
|
...(isRecord(reasoning) ? { reasoning } : {}),
|
||||||
|
...(typeof promptCacheKey === "string" ? { prompt_cache_key: promptCacheKey } : {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options.js"
|
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
|
||||||
import { Route, type RouteDefaultsInput } from "../route/client.js"
|
import { Route, type RouteDefaultsInput } from "../route/client"
|
||||||
import { Endpoint } from "../route/endpoint.js"
|
import { Endpoint } from "../route/endpoint"
|
||||||
import { HttpOptions, ProviderID, type ModelID, type ProviderOptions } from "../schema/index.js"
|
import { HttpOptions, ProviderID, type ModelID, type ProviderOptions } from "../schema"
|
||||||
import * as OpenAICompatibleProfiles from "./openai-compatible-profile.js"
|
import * as OpenAICompatibleProfiles from "./openai-compatible-profile"
|
||||||
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat.js"
|
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat"
|
||||||
import * as OpenAIChat from "../protocols/openai-chat.js"
|
import * as OpenAIChat from "../protocols/openai-chat"
|
||||||
import * as OpenAIResponses from "../protocols/openai-responses.js"
|
import * as OpenAIResponses from "../protocols/openai-responses"
|
||||||
import { XAIImages } from "../protocols/xai-images.js"
|
import { XAIImages } from "../protocols/xai-images"
|
||||||
import type { OpenAIOptionsInput } from "./openai-options.js"
|
import type { OpenAIOptionsInput } from "./openai-options"
|
||||||
import type { ProviderPackage } from "../provider-package.js"
|
import type { ProviderPackage } from "../provider-package"
|
||||||
|
|
||||||
export const id = ProviderID.make("xai")
|
export const id = ProviderID.make("xai")
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export interface Settings extends ProviderPackage.Settings {
|
|||||||
readonly providerOptions?: XAIProviderOptionsInput
|
readonly providerOptions?: XAIProviderOptionsInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { XAIImageOptions } from "../protocols/xai-images.js"
|
export type { XAIImageOptions } from "../protocols/xai-images"
|
||||||
|
|
||||||
const responsesRoute = Route.make({
|
const responsesRoute = Route.make({
|
||||||
id: "openai-responses",
|
id: "openai-responses",
|
||||||
@@ -47,8 +47,6 @@ const chatRoute = Route.make({
|
|||||||
protocol: OpenAIChat.protocol,
|
protocol: OpenAIChat.protocol,
|
||||||
endpoint: Endpoint.path("/chat/completions", { baseURL: OpenAICompatibleProfiles.profiles.xai.baseURL }),
|
endpoint: Endpoint.path("/chat/completions", { baseURL: OpenAICompatibleProfiles.profiles.xai.baseURL }),
|
||||||
transport: OpenAICompatibleChat.route.transport,
|
transport: OpenAICompatibleChat.route.transport,
|
||||||
headers: ({ request }): Record<string, string> =>
|
|
||||||
request.promptCacheKey ? { "x-grok-conv-id": request.promptCacheKey } : {},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const routes = [responsesRoute, chatRoute]
|
export const routes = [responsesRoute, chatRoute]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ZAIImages } from "../protocols/zai-images.js"
|
import { ZAIImages } from "../protocols/zai-images"
|
||||||
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options.js"
|
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
|
||||||
import { HttpOptions, ProviderID, type ModelID } from "../schema/index.js"
|
import { HttpOptions, ProviderID, type ModelID } from "../schema"
|
||||||
|
|
||||||
export const id = ProviderID.make("zai")
|
export const id = ProviderID.make("zai")
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ export type Config = ProviderAuthOption<"optional"> & {
|
|||||||
readonly http?: HttpOptions.Input
|
readonly http?: HttpOptions.Input
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { ZAIImageOptions } from "../protocols/zai-images.js"
|
export type { ZAIImageOptions } from "../protocols/zai-images"
|
||||||
|
|
||||||
const auth = (options: ProviderAuthOption<"optional">) => AuthOptions.bearer(options, "ZAI_API_KEY")
|
const auth = (options: ProviderAuthOption<"optional">) => AuthOptions.bearer(options, "ZAI_API_KEY")
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from "./route/index.js"
|
export * from "./route/index"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Config, Redacted } from "effect"
|
import type { Config, Redacted } from "effect"
|
||||||
import { Auth } from "./auth.js"
|
import { Auth } from "./auth"
|
||||||
|
|
||||||
export type ApiKeyMode = "optional" | "required"
|
export type ApiKeyMode = "optional" | "required"
|
||||||
|
|
||||||
@@ -61,4 +61,4 @@ export const bearer = (
|
|||||||
.bearer()
|
.bearer()
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as AuthOptions from "./auth-options.js"
|
export * as AuthOptions from "./auth-options"
|
||||||
|
|||||||
@@ -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, AIError, type HttpOptions } from "../schema/index.js"
|
import { AuthenticationReason, InvalidRequestReason, AIError, type HttpOptions } from "../schema"
|
||||||
|
|
||||||
export class MissingCredentialError extends Error {
|
export class MissingCredentialError extends Error {
|
||||||
readonly _tag = "MissingCredentialError"
|
readonly _tag = "MissingCredentialError"
|
||||||
@@ -153,4 +153,4 @@ export const toEffect =
|
|||||||
(authInput: AuthInput): Effect.Effect<Headers.Headers, AIError> =>
|
(authInput: AuthInput): Effect.Effect<Headers.Headers, AIError> =>
|
||||||
input.apply(authInput).pipe(Effect.mapError(toAIError))
|
input.apply(authInput).pipe(Effect.mapError(toAIError))
|
||||||
|
|
||||||
export * as Auth from "./auth.js"
|
export * as Auth from "./auth"
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Cause, Context, Effect, Layer, Schema, Stream } from "effect"
|
import { Cause, Context, Effect, Layer, Schema, Stream } from "effect"
|
||||||
import * as Option from "effect/Option"
|
import * as Option from "effect/Option"
|
||||||
import { Auth } from "./auth.js"
|
import { Auth } from "./auth"
|
||||||
import { Endpoint, type EndpointPatch } from "./endpoint.js"
|
import { Endpoint, type EndpointPatch } from "./endpoint"
|
||||||
import { RequestExecutor } from "./executor.js"
|
import { RequestExecutor } from "./executor"
|
||||||
import { Framing } from "./framing.js"
|
import { Framing } from "./framing"
|
||||||
import { HttpTransport } from "./transport/index.js"
|
import { HttpTransport } from "./transport"
|
||||||
import type { HttpMiddleware, Transport, TransportRuntime } from "./transport/index.js"
|
import type { HttpMiddleware, Transport, TransportRuntime } from "./transport"
|
||||||
import { WebSocketExecutor } from "./transport/index.js"
|
import { WebSocketExecutor } from "./transport"
|
||||||
import type { Protocol } from "./protocol.js"
|
import type { Protocol } from "./protocol"
|
||||||
import { applyCachePolicy } from "../cache-policy.js"
|
import { applyCachePolicy } from "../cache-policy"
|
||||||
import * as ProviderShared from "../protocols/shared.js"
|
import * as ProviderShared from "../protocols/shared"
|
||||||
import type { ProtocolID, ProviderOptions } from "../schema/index.js"
|
import type { ProtocolID, ProviderOptions } from "../schema"
|
||||||
import {
|
import {
|
||||||
AIError,
|
AIError,
|
||||||
GenerationOptions,
|
GenerationOptions,
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
mergeGenerationOptions,
|
mergeGenerationOptions,
|
||||||
mergeHttpOptions,
|
mergeHttpOptions,
|
||||||
mergeProviderOptions,
|
mergeProviderOptions,
|
||||||
} from "../schema/index.js"
|
} from "../schema"
|
||||||
|
|
||||||
export interface RouteBody<Body> {
|
export interface RouteBody<Body> {
|
||||||
/** Schema for the validated provider-native body sent as the JSON request. */
|
/** Schema for the validated provider-native body sent as the JSON request. */
|
||||||
@@ -255,7 +255,13 @@ const requireTerminalEvent = (route: string) => (events: Stream.Stream<LLMEvent,
|
|||||||
if (LLMEvent.is.finish(event) || LLMEvent.is.providerError(event)) terminal = true
|
if (LLMEvent.is.finish(event) || LLMEvent.is.providerError(event)) terminal = true
|
||||||
return Effect.succeed(event)
|
return Effect.succeed(event)
|
||||||
}),
|
}),
|
||||||
Stream.onEnd(Effect.suspend(() => (terminal ? Effect.void : Effect.fail(incompleteStreamError(route))))),
|
Stream.onEnd(
|
||||||
|
Effect.suspend(() =>
|
||||||
|
terminal
|
||||||
|
? Effect.void
|
||||||
|
: Effect.fail(incompleteStreamError(route)),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { LLMRequest } from "../schema/index.js"
|
import type { LLMRequest } from "../schema"
|
||||||
import * as ProviderShared from "../protocols/shared.js"
|
import * as ProviderShared from "../protocols/shared"
|
||||||
|
|
||||||
export interface EndpointInput<Body> {
|
export interface EndpointInput<Body> {
|
||||||
readonly request: LLMRequest
|
readonly request: LLMRequest
|
||||||
@@ -53,4 +53,4 @@ export const render = <Body>(endpoint: Definition<Body>, input: EndpointInput<Bo
|
|||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as Endpoint from "./endpoint.js"
|
export * as Endpoint from "./endpoint"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Cause, Context, Effect, Layer, Option, Schema, Stream } from "effect"
|
import { Cause, Context, Effect, Layer } from "effect"
|
||||||
import {
|
import {
|
||||||
FetchHttpClient,
|
FetchHttpClient,
|
||||||
Headers,
|
Headers,
|
||||||
@@ -14,8 +14,8 @@ import {
|
|||||||
HttpResponseDetails,
|
HttpResponseDetails,
|
||||||
AIError,
|
AIError,
|
||||||
TransportReason,
|
TransportReason,
|
||||||
} from "../schema/index.js"
|
} from "../schema"
|
||||||
import { classifyProviderFailure } from "../provider-error.js"
|
import { classifyProviderFailure } from "../provider-error"
|
||||||
|
|
||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly execute: (
|
readonly execute: (
|
||||||
@@ -185,33 +185,21 @@ const secretValues = (request: HttpClientRequest.HttpClientRequest) => {
|
|||||||
// Two passes: structural (redact `"name": "value"` and `name=value` patterns
|
// Two passes: structural (redact `"name": "value"` and `name=value` patterns
|
||||||
// for any field name that looks sensitive) plus literal (replace any actual
|
// for any field name that looks sensitive) plus literal (replace any actual
|
||||||
// secret values we sent in the request, in case the response echoes one back).
|
// secret values we sent in the request, in case the response echoes one back).
|
||||||
const redactBody = (body: string, secrets: ReadonlySet<string>) =>
|
const redactBody = (body: string, request: HttpClientRequest.HttpClientRequest) =>
|
||||||
Array.from(secrets).reduce(
|
Array.from(secretValues(request)).reduce(
|
||||||
(text, secret) => text.split(secret).join(REDACTED),
|
(text, secret) => text.split(secret).join(REDACTED),
|
||||||
body.replace(REDACT_JSON_FIELD, `$1"${REDACTED}"`).replace(REDACT_QUERY_FIELD, `$1${REDACTED}`),
|
body.replace(REDACT_JSON_FIELD, `$1"${REDACTED}"`).replace(REDACT_QUERY_FIELD, `$1${REDACTED}`),
|
||||||
)
|
)
|
||||||
|
|
||||||
const responseBody = (body: string | void, secrets: ReadonlySet<string>) => {
|
const responseBody = (body: string | void, request: HttpClientRequest.HttpClientRequest) => {
|
||||||
if (body === undefined) return {}
|
if (body === undefined) return {}
|
||||||
const redacted = redactBody(body, secrets)
|
const redacted = redactBody(body, request)
|
||||||
if (redacted.length <= BODY_LIMIT) return { body: redacted }
|
if (redacted.length <= BODY_LIMIT) return { body: redacted }
|
||||||
return { body: redacted.slice(0, BODY_LIMIT), bodyTruncated: true }
|
return { body: redacted.slice(0, BODY_LIMIT), bodyTruncated: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
const decodeProviderBody = Schema.decodeUnknownOption(
|
|
||||||
Schema.fromJsonString(
|
|
||||||
Schema.Struct({
|
|
||||||
message: Schema.optionalKey(Schema.String),
|
|
||||||
error: Schema.optionalKey(Schema.Struct({ message: Schema.optionalKey(Schema.String) })),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const providerMessage = (status: number, body: { readonly body?: string }) => {
|
const providerMessage = (status: number, body: { readonly body?: string }) => {
|
||||||
if (body.body && body.body.length <= 500) {
|
if (body.body && body.body.length <= 500) return `Provider request failed with HTTP ${status}: ${body.body}`
|
||||||
const decoded = Option.getOrUndefined(decodeProviderBody(body.body))
|
|
||||||
return `Provider request failed with HTTP ${status}: ${decoded?.error?.message ?? decoded?.message ?? body.body}`
|
|
||||||
}
|
|
||||||
return `Provider request failed with HTTP ${status}`
|
return `Provider request failed with HTTP ${status}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +228,7 @@ const statusError =
|
|||||||
const headers = normalizedHeaders(response.headers)
|
const headers = normalizedHeaders(response.headers)
|
||||||
const retryAfter = retryAfterMs(headers)
|
const retryAfter = retryAfterMs(headers)
|
||||||
const rateLimit = rateLimitDetails(headers, retryAfter)
|
const rateLimit = rateLimitDetails(headers, retryAfter)
|
||||||
const details = responseBody(body, secretValues(request))
|
const details = responseBody(body, request)
|
||||||
return yield* new AIError({
|
return yield* new AIError({
|
||||||
module: "RequestExecutor",
|
module: "RequestExecutor",
|
||||||
method: "execute",
|
method: "execute",
|
||||||
@@ -261,122 +249,44 @@ const statusError =
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Classifies an HTTP failure captured outside the executor (for example by the
|
const toHttpError = (redactedNames: ReadonlyArray<string | RegExp>) => (error: unknown) => {
|
||||||
// AI SDK's own fetch) onto the same reason types and redacted HttpContext that
|
const transportError = (input: {
|
||||||
// executor-driven requests produce. The originating request is not available on
|
readonly message: string
|
||||||
// that path, so the method is assumed (language model calls are always POST),
|
readonly kind?: string | undefined
|
||||||
// request headers are empty, and only structural body redaction applies.
|
readonly request?: HttpClientRequest.HttpClientRequest | undefined
|
||||||
export const classifyHttpFailure = (input: {
|
}) =>
|
||||||
readonly message: string
|
|
||||||
readonly url: string
|
|
||||||
readonly status?: number | undefined
|
|
||||||
readonly code?: string | undefined
|
|
||||||
readonly responseHeaders?: Record<string, string> | undefined
|
|
||||||
readonly responseBody?: string | undefined
|
|
||||||
}) => {
|
|
||||||
const headers = normalizedHeaders(Headers.fromInput(input.responseHeaders))
|
|
||||||
const retryAfter = retryAfterMs(headers)
|
|
||||||
const rateLimit = rateLimitDetails(headers, retryAfter)
|
|
||||||
const details = responseBody(input.responseBody ?? undefined, new Set<string>())
|
|
||||||
return classifyProviderFailure({
|
|
||||||
message: input.message,
|
|
||||||
status: input.status,
|
|
||||||
code: input.code,
|
|
||||||
retryAfterMs: retryAfter,
|
|
||||||
rateLimit,
|
|
||||||
http: new HttpContext({
|
|
||||||
request: new HttpRequestDetails({ method: "POST", url: redactUrl(input.url), headers: {} }),
|
|
||||||
response:
|
|
||||||
input.status === undefined
|
|
||||||
? undefined
|
|
||||||
: new HttpResponseDetails({ status: input.status, headers: redactHeaders(Headers.fromInput(headers), []) }),
|
|
||||||
...details,
|
|
||||||
requestId: requestId(headers),
|
|
||||||
rateLimit,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type HttpOperation = "request" | "read"
|
|
||||||
|
|
||||||
const NativeTransportFailure = Schema.Struct({
|
|
||||||
message: Schema.String,
|
|
||||||
code: Schema.optionalKey(Schema.String),
|
|
||||||
cause: Schema.optionalKey(Schema.Unknown),
|
|
||||||
})
|
|
||||||
const decodeNativeTransportFailure = Schema.decodeUnknownOption(NativeTransportFailure)
|
|
||||||
|
|
||||||
const nativeTransportFailure = (error: unknown) => {
|
|
||||||
const failure = Option.getOrUndefined(decodeNativeTransportFailure(error))
|
|
||||||
if (!failure) return undefined
|
|
||||||
if (failure.code !== undefined) return failure
|
|
||||||
const cause = Option.getOrUndefined(decodeNativeTransportFailure(failure.cause))
|
|
||||||
if (cause?.code !== undefined) return cause
|
|
||||||
return failure
|
|
||||||
}
|
|
||||||
|
|
||||||
const httpError = (input: {
|
|
||||||
readonly error: unknown
|
|
||||||
readonly request: HttpClientRequest.HttpClientRequest
|
|
||||||
readonly operation: HttpOperation
|
|
||||||
readonly redactedNames: ReadonlyArray<string | RegExp>
|
|
||||||
}) => {
|
|
||||||
const request = HttpClientError.isHttpClientError(input.error) ? input.error.request : input.request
|
|
||||||
const transportError = (failure: { readonly message: string; readonly code?: string | undefined }) =>
|
|
||||||
new AIError({
|
new AIError({
|
||||||
module: "RequestExecutor",
|
module: "RequestExecutor",
|
||||||
method: input.operation,
|
method: "execute",
|
||||||
reason: new TransportReason({
|
reason: new TransportReason({
|
||||||
message: failure.message,
|
message: input.message,
|
||||||
transport: "http",
|
kind: input.kind,
|
||||||
operation: input.operation,
|
url: input.request ? redactUrl(input.request.url) : undefined,
|
||||||
code: failure.code,
|
http: input.request ? new HttpContext({ request: requestDetails(input.request, redactedNames) }) : undefined,
|
||||||
url: redactUrl(request.url),
|
|
||||||
http: new HttpContext({ request: requestDetails(request, input.redactedNames) }),
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const source =
|
if (Cause.isTimeoutError(error)) {
|
||||||
HttpClientError.isHttpClientError(input.error) && "cause" in input.error.reason
|
return transportError({ message: error.message, kind: "Timeout" })
|
||||||
? input.error.reason.cause
|
}
|
||||||
: input.error
|
if (!HttpClientError.isHttpClientError(error)) {
|
||||||
const native = nativeTransportFailure(source)
|
return transportError({ message: error instanceof Error ? error.message : "HTTP transport failed" })
|
||||||
const code = native?.code
|
}
|
||||||
const raw = native?.message ?? (input.error instanceof Error ? input.error.message : undefined)
|
const request = "request" in error ? error.request : undefined
|
||||||
const detail = raw ? redactBody(raw, secretValues(request)) : undefined
|
if (error.reason._tag === "TransportError") {
|
||||||
const message = code && detail && !detail.includes(code) ? `${code}: ${detail}` : detail
|
|
||||||
|
|
||||||
if (Cause.isTimeoutError(input.error) || Cause.isTimeoutError(source))
|
|
||||||
return transportError({ message: message ?? "HTTP transport timed out", code: code ?? "Timeout" })
|
|
||||||
if (!HttpClientError.isHttpClientError(input.error))
|
|
||||||
return transportError({ message: message ?? "HTTP transport failed", code })
|
|
||||||
if (input.error.reason._tag === "TransportError") {
|
|
||||||
return transportError({
|
return transportError({
|
||||||
message: message ?? input.error.reason.description ?? "HTTP transport failed",
|
message: error.reason.description ?? "HTTP transport failed",
|
||||||
code: code ?? input.error.reason._tag,
|
kind: error.reason._tag,
|
||||||
|
request,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return transportError({
|
return transportError({
|
||||||
message: message ?? `HTTP transport failed: ${input.error.reason._tag}`,
|
message: `HTTP transport failed: ${error.reason._tag}`,
|
||||||
code: code ?? input.error.reason._tag,
|
kind: error.reason._tag,
|
||||||
|
request,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const stream = (
|
|
||||||
executor: Interface,
|
|
||||||
request: HttpClientRequest.HttpClientRequest,
|
|
||||||
middleware?: HttpMiddleware,
|
|
||||||
): Stream.Stream<Uint8Array, AIError> =>
|
|
||||||
Stream.unwrap(
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const redactedNames = yield* Headers.CurrentRedactedNames
|
|
||||||
const response = yield* executor.execute(request, middleware)
|
|
||||||
return response.stream.pipe(
|
|
||||||
Stream.mapError((error) => httpError({ error, request: response.request, operation: "read", redactedNames })),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const layer: Layer.Layer<Service, never, HttpClient.HttpClient> = Layer.effect(
|
export const layer: Layer.Layer<Service, never, HttpClient.HttpClient> = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
@@ -385,16 +295,15 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient> = Layer.e
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const redactedNames = yield* Headers.CurrentRedactedNames
|
const redactedNames = yield* Headers.CurrentRedactedNames
|
||||||
if (!middleware)
|
if (!middleware)
|
||||||
return yield* http.execute(request).pipe(
|
return yield* http
|
||||||
Effect.mapError((error) => httpError({ error, request, operation: "request", redactedNames })),
|
.execute(request)
|
||||||
Effect.flatMap(statusError(request, redactedNames)),
|
.pipe(Effect.mapError(toHttpError(redactedNames)), Effect.flatMap(statusError(request, redactedNames)))
|
||||||
)
|
|
||||||
|
|
||||||
const response = yield* middleware(request, (input) =>
|
const response = yield* middleware(request, (input) =>
|
||||||
http
|
http
|
||||||
.execute(input)
|
.execute(input)
|
||||||
.pipe(Effect.mapError((cause) => (cause instanceof Error ? cause : new Error(String(cause))))),
|
.pipe(Effect.mapError((cause) => (cause instanceof Error ? cause : new Error(String(cause))))),
|
||||||
).pipe(Effect.mapError((error) => httpError({ error, request, operation: "request", redactedNames })))
|
).pipe(Effect.mapError(toHttpError(redactedNames)))
|
||||||
return yield* statusError(response.request, redactedNames)(response)
|
return yield* statusError(response.request, redactedNames)(response)
|
||||||
})
|
})
|
||||||
return Service.of({
|
return Service.of({
|
||||||
@@ -405,4 +314,4 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient> = Layer.e
|
|||||||
|
|
||||||
export const fetchLayer = layer.pipe(Layer.provide(FetchHttpClient.layer))
|
export const fetchLayer = layer.pipe(Layer.provide(FetchHttpClient.layer))
|
||||||
|
|
||||||
export * as RequestExecutor from "./executor.js"
|
export * as RequestExecutor from "./executor"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Stream } from "effect"
|
import type { Stream } from "effect"
|
||||||
import * as ProviderShared from "../protocols/shared.js"
|
import * as ProviderShared from "../protocols/shared"
|
||||||
import type { AIError } from "../schema/index.js"
|
import type { AIError } from "../schema"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode a streaming HTTP response body into provider-protocol frames.
|
* Decode a streaming HTTP response body into provider-protocol frames.
|
||||||
@@ -24,4 +24,4 @@ export interface Definition<Frame> {
|
|||||||
/** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
|
/** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
|
||||||
export const sse: Definition<string> = { id: "sse", frame: ProviderShared.sseFraming }
|
export const sse: Definition<string> = { id: "sse", frame: ProviderShared.sseFraming }
|
||||||
|
|
||||||
export * as Framing from "./framing.js"
|
export * as Framing from "./framing"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export { Route, LLMClient } from "./client.js"
|
export { Route, LLMClient } from "./client"
|
||||||
export type {
|
export type {
|
||||||
Route as RouteShape,
|
Route as RouteShape,
|
||||||
RouteLanguageModelInput,
|
RouteLanguageModelInput,
|
||||||
@@ -9,18 +9,18 @@ export type {
|
|||||||
Interface as LLMClientShape,
|
Interface as LLMClientShape,
|
||||||
Service as LLMClientService,
|
Service as LLMClientService,
|
||||||
StreamOptions,
|
StreamOptions,
|
||||||
} from "./client.js"
|
} from "./client"
|
||||||
export * from "./executor.js"
|
export * from "./executor"
|
||||||
export { Auth } from "./auth.js"
|
export { Auth } from "./auth"
|
||||||
export { AuthOptions } from "./auth-options.js"
|
export { AuthOptions } from "./auth-options"
|
||||||
export { Endpoint } from "./endpoint.js"
|
export { Endpoint } from "./endpoint"
|
||||||
export { Framing } from "./framing.js"
|
export { Framing } from "./framing"
|
||||||
export { Protocol } from "./protocol.js"
|
export { Protocol } from "./protocol"
|
||||||
export { HttpTransport, WebSocketExecutor, WebSocketTransport } from "./transport/index.js"
|
export { HttpTransport, WebSocketExecutor, WebSocketTransport } from "./transport"
|
||||||
export * as Transport from "./transport/index.js"
|
export * as Transport from "./transport"
|
||||||
export type { Definition as AuthShape, AuthInput, Credential, CredentialError } from "./auth.js"
|
export type { Definition as AuthShape, AuthInput, Credential, CredentialError } from "./auth"
|
||||||
export type { ApiKeyMode, AuthOverride, ProviderAuthOption } from "./auth-options.js"
|
export type { ApiKeyMode, AuthOverride, ProviderAuthOption } from "./auth-options"
|
||||||
export type { Definition as EndpointFn, EndpointInput } from "./endpoint.js"
|
export type { Definition as EndpointFn, EndpointInput } from "./endpoint"
|
||||||
export type { Definition as FramingDef } from "./framing.js"
|
export type { Definition as FramingDef } from "./framing"
|
||||||
export type { Protocol as ProtocolDef } from "./protocol.js"
|
export type { Protocol as ProtocolDef } from "./protocol"
|
||||||
export type { HttpHandler, HttpMiddleware, Transport as TransportDef, TransportRuntime } from "./transport/index.js"
|
export type { HttpHandler, HttpMiddleware, Transport as TransportDef, TransportRuntime } from "./transport"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user