From af5c3ddb2fe5dbf5e3fb31dc102decf0ec82063c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Sun, 12 Apr 2026 10:55:44 +0200 Subject: [PATCH 001/616] feat(opencode-router): MVP multi-user multi-session opencode web via Kubernetes pod isolation (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses #20067 (as MVP / alternative approach) ## Intent `opencode serve` runs a single-user server — everyone hitting the same instance shares sessions, filesystem, and API keys. This is the core problem raised in #20067. This PR takes a **proxy-based approach** rather than adding auth into `opencode serve` itself: a lightweight router sits in front of one `opencode serve` pod *per session*, providing: - **Multi-user isolation** — each authenticated user gets their own pod with their own filesystem and conversation history - **Multi-session isolation** — each (user, repo, branch) triple gets its own dedicated pod, matching the Claude Code web UX where separate coding sessions are independent - **Per-session credentials** — API keys are injected per-pod via Kubernetes Secrets, enabling per-user or per-team key management at the infrastructure level - **Auth delegation** — identity comes from an upstream auth proxy (oauth2-proxy with GitHub OAuth), so `opencode serve` itself needs no auth changes This is an MVP. It is opinionated about the deployment target (Kubernetes) and auth mechanism (oauth2-proxy), but the router itself is a standard Node.js HTTP/WebSocket proxy that could front other auth mechanisms. ## Architecture ``` Browser │ HTTPS (-oc. or opencode-router.) ▼ Cloudflare Tunnel → Traefik ├─ opencode-router. ──[oauth2-chain]──▶ opencode-router (:3000) │ │ │ setup SPA: pick repo + branch │ │ POST /api/sessions │ ▼ │ K8s: create Pod + PVC │ (one per email+repo+branch) │ └─ -oc. ──[oauth2-chain]──▶ opencode-router (:3000) │ proxy by hash from Host header ▼ opencode-session- Pod (:4096) (opencode serve, isolated filesystem) ``` Each session gets a **dedicated subdomain** (`-oc.`) — the router identifies which pod to proxy to by extracting the hash from the `Host` header. This is the same pattern used by Claude Code web, where each coding session has its own URL. ## Major changes **New: `packages/opencode-router/`** — HTTP/WebSocket reverse proxy - `src/pod-manager.ts` — creates/deletes Kubernetes Pods and PVCs on demand; session key is `sha256(email + repoUrl + branch)[0:12]`; full `restricted` PSS-compliant securityContext; git-init init container clones the repo on first pod creation - `src/api.ts` — `GET /api/sessions`, `POST /api/sessions {repoUrl, branch}`, `GET /api/sessions/:hash`; session URLs include `ROUTE_SUFFIX` for first-level subdomain TLS coverage - `src/index.ts` — HTTP server + WebSocket upgrade handler; `getSessionHash()` extracts session from `Host` header; background idle pod cleanup - `src/config.ts` — `ROUTER_DOMAIN` (required base domain), `ROUTE_SUFFIX` (default `""`, set to `-oc` in production), plus K8s/storage config - `Dockerfile` — multi-stage build from monorepo root - `docs/deployment.md` — full Kubernetes deployment guide (RBAC, Secrets, Ingress, WebSocket config) - `docs/adr-001-per-user-pod-isolation.md` — rationale for proxy pattern over in-process auth - `docs/adr-002-restricted-pss-securitycontext.md` — Kubernetes security hardening **New: `packages/opencode-router-app/`** — session management SPA - Session picker: choose repo URL + branch, displays existing sessions for the authenticated user - Loading screen while pod is initialising (polls `GET /api/sessions/:hash`) - Redirects to the session URL once the pod is running **`ROUTE_SUFFIX` design** — session hostnames are `.`. The suffix (e.g. `-oc`) keeps sessions at the first subdomain level, covered by a standard `*.` wildcard TLS certificate. Without it, sessions at `.opencode-router.` (second level) would require a paid certificate product. ## Side effects - `ROUTER_DOMAIN` is a **required** env var - `ROUTE_SUFFIX` defaults to `""` (backward compatible); set to `-oc` in production - PVCs are never deleted — only pods are removed on idle timeout; storage must be monitored externally - `DEV_EMAIL` enables local dev auth bypass; must never be set in production - The router is stateless across replicas; the in-process idle-cleanup timer and activity throttle run independently on each replica (acceptable for homelab scale) --- bun.lock | 136 +++- .../opencode-router-app/.env.local.example | 3 + packages/opencode-router-app/.gitignore | 3 + packages/opencode-router-app/index.html | 13 + packages/opencode-router-app/package.json | 24 + packages/opencode-router-app/src/api.ts | 50 ++ packages/opencode-router-app/src/app.tsx | 140 ++++ packages/opencode-router-app/src/entry.tsx | 13 + packages/opencode-router-app/src/index.css | 2 + .../src/loading-screen.tsx | 40 + .../opencode-router-app/src/setup-form.tsx | 70 ++ packages/opencode-router-app/tsconfig.json | 20 + packages/opencode-router-app/vite.config.ts | 16 + packages/opencode-router/.env.local.example | 50 ++ packages/opencode-router/.gitignore | 3 + packages/opencode-router/Dockerfile | 81 ++ packages/opencode-router/README.md | 120 +++ .../docs/adr-001-per-user-pod-isolation.md | 164 ++++ .../adr-002-restricted-pss-securitycontext.md | 93 +++ packages/opencode-router/docs/deployment.md | 628 ++++++++++++++++ packages/opencode-router/package.json | 22 + packages/opencode-router/pnpm-lock.yaml | 699 ++++++++++++++++++ .../scripts/create-local-kubeconfig.sh | 57 ++ .../scripts/port-forward-pod.sh | 47 ++ packages/opencode-router/src/api.ts | 94 +++ packages/opencode-router/src/config.ts | 50 ++ packages/opencode-router/src/index.ts | 153 ++++ packages/opencode-router/src/pod-manager.ts | 403 ++++++++++ packages/opencode-router/src/static.ts | 48 ++ packages/opencode-router/tsconfig.json | 14 + 30 files changed, 3252 insertions(+), 4 deletions(-) create mode 100644 packages/opencode-router-app/.env.local.example create mode 100644 packages/opencode-router-app/.gitignore create mode 100644 packages/opencode-router-app/index.html create mode 100644 packages/opencode-router-app/package.json create mode 100644 packages/opencode-router-app/src/api.ts create mode 100644 packages/opencode-router-app/src/app.tsx create mode 100644 packages/opencode-router-app/src/entry.tsx create mode 100644 packages/opencode-router-app/src/index.css create mode 100644 packages/opencode-router-app/src/loading-screen.tsx create mode 100644 packages/opencode-router-app/src/setup-form.tsx create mode 100644 packages/opencode-router-app/tsconfig.json create mode 100644 packages/opencode-router-app/vite.config.ts create mode 100644 packages/opencode-router/.env.local.example create mode 100644 packages/opencode-router/.gitignore create mode 100644 packages/opencode-router/Dockerfile create mode 100644 packages/opencode-router/README.md create mode 100644 packages/opencode-router/docs/adr-001-per-user-pod-isolation.md create mode 100644 packages/opencode-router/docs/adr-002-restricted-pss-securitycontext.md create mode 100644 packages/opencode-router/docs/deployment.md create mode 100644 packages/opencode-router/package.json create mode 100644 packages/opencode-router/pnpm-lock.yaml create mode 100755 packages/opencode-router/scripts/create-local-kubeconfig.sh create mode 100755 packages/opencode-router/scripts/port-forward-pod.sh create mode 100644 packages/opencode-router/src/api.ts create mode 100644 packages/opencode-router/src/config.ts create mode 100644 packages/opencode-router/src/index.ts create mode 100644 packages/opencode-router/src/pod-manager.ts create mode 100644 packages/opencode-router/src/static.ts create mode 100644 packages/opencode-router/tsconfig.json diff --git a/bun.lock b/bun.lock index 1a16a37698a4..044d9aa17151 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 1, "workspaces": { "": { "name": "opencode", @@ -426,6 +425,37 @@ "zod-to-json-schema": "3.24.5", }, }, + "packages/opencode-router": { + "name": "@opencode-ai/opencode-router", + "version": "0.0.1", + "dependencies": { + "@kubernetes/client-node": "^1.0.0", + "http-proxy": "^1.18.1", + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.0", + "@types/http-proxy": "^1.17.16", + "@types/node": "^22.0.0", + "typescript": "^5.7.0", + }, + }, + "packages/opencode-router-app": { + "name": "@opencode-ai/opencode-router-app", + "version": "0.0.1", + "dependencies": { + "@kobalte/core": "catalog:", + "@opencode-ai/ui": "workspace:*", + "solid-js": "catalog:", + }, + "devDependencies": { + "@tailwindcss/vite": "catalog:", + "@tsconfig/node22": "catalog:", + "tailwindcss": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plugin-solid": "catalog:", + }, + }, "packages/plugin": { "name": "@opencode-ai/plugin", "version": "1.3.13", @@ -1288,6 +1318,10 @@ "@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="], + "@jsep-plugin/assignment": ["@jsep-plugin/assignment@1.3.0", "", { "peerDependencies": { "jsep": "^0.4.0||^1.0.0" } }, "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ=="], + + "@jsep-plugin/regex": ["@jsep-plugin/regex@1.0.4", "", { "peerDependencies": { "jsep": "^0.4.0||^1.0.0" } }, "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg=="], + "@jsx-email/all": ["@jsx-email/all@2.2.3", "", { "dependencies": { "@jsx-email/body": "1.0.2", "@jsx-email/button": "1.0.4", "@jsx-email/column": "1.0.3", "@jsx-email/container": "1.0.2", "@jsx-email/font": "1.0.3", "@jsx-email/head": "1.0.2", "@jsx-email/heading": "1.0.2", "@jsx-email/hr": "1.0.2", "@jsx-email/html": "1.0.2", "@jsx-email/img": "1.0.2", "@jsx-email/link": "1.0.2", "@jsx-email/markdown": "2.0.4", "@jsx-email/preview": "1.0.2", "@jsx-email/render": "1.1.1", "@jsx-email/row": "1.0.2", "@jsx-email/section": "1.0.2", "@jsx-email/tailwind": "2.4.4", "@jsx-email/text": "1.0.2" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-OBvLe/hVSQc0LlMSTJnkjFoqs3bmxcC4zpy/5pT5agPCSKMvAKQjzmsc2xJ2wO73jSpRV1K/g38GmvdCfrhSoQ=="], "@jsx-email/body": ["@jsx-email/body@1.0.2", "", { "peerDependencies": { "react": "^18.2.0" } }, "sha512-NjR2tgLH4XGfGkm+O8kcVwi9MBqZsXZCLlmk3HlMux3/n/+a5zB+yhJqXWZBJl2i+6cSF+E2O6hK11ekyK9WWQ=="], @@ -1334,6 +1368,8 @@ "@kobalte/utils": ["@kobalte/utils@0.9.1", "", { "dependencies": { "@solid-primitives/event-listener": "^2.2.14", "@solid-primitives/keyed": "^1.2.0", "@solid-primitives/map": "^0.4.7", "@solid-primitives/media": "^2.2.4", "@solid-primitives/props": "^3.1.8", "@solid-primitives/refs": "^1.0.5", "@solid-primitives/utils": "^6.2.1" }, "peerDependencies": { "solid-js": "^1.8.8" } }, "sha512-eeU60A3kprIiBDAfv9gUJX1tXGLuZiKMajUfSQURAF2pk4ZoMYiqIzmrMBvzcxP39xnYttgTyQEVLwiTZnrV4w=="], + "@kubernetes/client-node": ["@kubernetes/client-node@1.4.0", "", { "dependencies": { "@types/js-yaml": "^4.0.1", "@types/node": "^24.0.0", "@types/node-fetch": "^2.6.13", "@types/stream-buffers": "^3.0.3", "form-data": "^4.0.0", "hpagent": "^1.2.0", "isomorphic-ws": "^5.0.0", "js-yaml": "^4.1.0", "jsonpath-plus": "^10.3.0", "node-fetch": "^2.7.0", "openid-client": "^6.1.3", "rfc4648": "^1.3.0", "socks-proxy-agent": "^8.0.4", "stream-buffers": "^3.0.2", "tar-fs": "^3.0.9", "ws": "^8.18.2" } }, "sha512-Zge3YvF7DJi264dU1b3wb/GmzR99JhUpqTvp+VGHfwZT+g7EOOYNScDJNZwXy9cszyIGPIs0VHr+kk8e95qqrA=="], + "@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="], "@leichtgewicht/ip-codec": ["@leichtgewicht/ip-codec@2.0.5", "", {}, "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="], @@ -1478,6 +1514,10 @@ "@opencode-ai/function": ["@opencode-ai/function@workspace:packages/function"], + "@opencode-ai/opencode-router": ["@opencode-ai/opencode-router@workspace:packages/opencode-router"], + + "@opencode-ai/opencode-router-app": ["@opencode-ai/opencode-router-app@workspace:packages/opencode-router-app"], + "@opencode-ai/plugin": ["@opencode-ai/plugin@workspace:packages/plugin"], "@opencode-ai/script": ["@opencode-ai/script@workspace:packages/script"], @@ -1962,7 +2002,7 @@ "@solidjs/router": ["@solidjs/router@0.15.4", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ=="], - "@solidjs/start": ["@solidjs/start@https://pkg.pr.new/@solidjs/start@dfb2020", { "dependencies": { "@babel/core": "^7.28.3", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.5", "@solidjs/meta": "^0.29.4", "@tanstack/server-functions-plugin": "1.134.5", "@types/babel__traverse": "^7.28.0", "@types/micromatch": "^4.0.9", "cookie-es": "^2.0.0", "defu": "^6.1.4", "error-stack-parser": "^2.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.3", "fast-glob": "^3.3.3", "h3": "npm:h3@2.0.1-rc.4", "html-to-image": "^1.11.13", "micromatch": "^4.0.8", "path-to-regexp": "^8.2.0", "pathe": "^2.0.3", "radix3": "^1.1.2", "seroval": "^1.3.2", "seroval-plugins": "^1.2.1", "shiki": "^1.26.1", "solid-js": "^1.9.9", "source-map-js": "^1.2.1", "srvx": "^0.9.1", "terracotta": "^1.0.6", "vite": "7.1.10", "vite-plugin-solid": "^2.11.9", "vitest": "^4.0.10" } }, "sha512-7JjjA49VGNOsMRI8QRUhVudZmv0CnJ18SliSgK1ojszs/c3ijftgVkzvXdkSLN4miDTzbkXewf65D6ZBo6W+GQ=="], + "@solidjs/start": ["@solidjs/start@https://pkg.pr.new/@solidjs/start@dfb2020", { "dependencies": { "@babel/core": "^7.28.3", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.5", "@solidjs/meta": "^0.29.4", "@tanstack/server-functions-plugin": "1.134.5", "@types/babel__traverse": "^7.28.0", "@types/micromatch": "^4.0.9", "cookie-es": "^2.0.0", "defu": "^6.1.4", "error-stack-parser": "^2.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.3", "fast-glob": "^3.3.3", "h3": "npm:h3@2.0.1-rc.4", "html-to-image": "^1.11.13", "micromatch": "^4.0.8", "path-to-regexp": "^8.2.0", "pathe": "^2.0.3", "radix3": "^1.1.2", "seroval": "^1.3.2", "seroval-plugins": "^1.2.1", "shiki": "^1.26.1", "solid-js": "^1.9.9", "source-map-js": "^1.2.1", "srvx": "^0.9.1", "terracotta": "^1.0.6", "vite": "7.1.10", "vite-plugin-solid": "^2.11.9", "vitest": "^4.0.10" } }], "@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="], @@ -2158,6 +2198,8 @@ "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], + "@types/http-proxy": ["@types/http-proxy@1.17.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw=="], + "@types/is-stream": ["@types/is-stream@1.1.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg=="], "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="], @@ -2234,6 +2276,8 @@ "@types/ssri": ["@types/ssri@7.1.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-odD/56S3B51liILSk5aXJlnYt99S6Rt9EFDDqGtJM26rKHApHcwyU/UoYHrzKkdkHMAIquGWCuHtQTbes+FRQw=="], + "@types/stream-buffers": ["@types/stream-buffers@3.0.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-J+7VaHKNvlNPJPEJXX/fKa9DZtR/xPMwuIbe+yNOwp1YB+ApUOBv2aUpEoBJEi8nJgbgs1x8e73ttg0r1rSUdw=="], + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], "@types/tsscmp": ["@types/tsscmp@1.0.2", "", {}, "sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g=="], @@ -2958,7 +3002,7 @@ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], - "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], + "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], @@ -3124,7 +3168,7 @@ "get-tsconfig": ["get-tsconfig@4.13.7", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q=="], - "ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#4af877d", {}, "anomalyco-ghostty-web-4af877d", "sha512-fbEK8mtr7ar4ySsF+JUGjhaZrane7dKphanN+SxHt5XXI6yLMAh/Hpf6sNCOyyVa2UlGCd7YpXG/T2v2RUAX+A=="], + "ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#4af877d", {}, "anomalyco-ghostty-web-4af877d"], "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="], @@ -3234,6 +3278,8 @@ "hosted-git-info": ["hosted-git-info@9.0.2", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg=="], + "hpagent": ["hpagent@1.2.0", "", {}, "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA=="], + "html-entities": ["html-entities@2.3.3", "", {}, "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="], "html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="], @@ -3254,6 +3300,8 @@ "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + "http-proxy": ["http-proxy@1.18.1", "", { "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" } }, "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ=="], + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="], @@ -3444,6 +3492,8 @@ "jsbi": ["jsbi@4.3.2", "", {}, "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew=="], + "jsep": ["jsep@1.4.0", "", {}, "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw=="], + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], @@ -3476,6 +3526,8 @@ "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], + "jsonpath-plus": ["jsonpath-plus@10.4.0", "", { "dependencies": { "@jsep-plugin/assignment": "^1.3.0", "@jsep-plugin/regex": "^1.0.4", "jsep": "^1.4.0" }, "bin": { "jsonpath": "bin/jsonpath-cli.js", "jsonpath-plus": "bin/jsonpath-cli.js" } }, "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA=="], + "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], "just-diff": ["just-diff@6.0.2", "", {}, "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA=="], @@ -3872,6 +3924,8 @@ "nypm": ["nypm@0.6.5", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ=="], + "oauth4webapi": ["oauth4webapi@3.8.5", "", {}, "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="], @@ -4252,6 +4306,8 @@ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="], + "resedit": ["resedit@1.7.2", "", { "dependencies": { "pe-library": "^0.4.1" } }, "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA=="], "reselect": ["reselect@4.1.8", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="], @@ -4286,6 +4342,8 @@ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "rfc4648": ["rfc4648@1.5.4", "", {}, "sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg=="], + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], "rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], @@ -4490,6 +4548,8 @@ "storybook-solidjs-vite": ["storybook-solidjs-vite@10.0.11", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.4", "@storybook/builder-vite": "^10.3.1", "@storybook/global": "^5.0.0", "vite-plugin-solid": "^2.11.11" }, "peerDependencies": { "solid-js": "^1.9.0", "storybook": "^0.0.0-0 || ^10.0.0", "typescript": "^4.0.0 || ^5.0.0 || ^6.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["typescript"] }, "sha512-aDe2ipD0HAH54zsQbbCUI9AcBPiLwljtdx+GRmyHsHaPyij1KmGVKo5N004qXZfQaoK0uru9DsdnWon5rRkjfg=="], + "stream-buffers": ["stream-buffers@3.0.3", "", {}, "sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw=="], + "stream-replace-string": ["stream-replace-string@2.0.0", "", {}, "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w=="], "streamx": ["streamx@2.25.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg=="], @@ -4550,6 +4610,8 @@ "tar": ["tar@7.5.13", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng=="], + "tar-fs": ["tar-fs@3.1.2", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw=="], + "tar-stream": ["tar-stream@3.1.8", "", { "dependencies": { "b4a": "^1.6.4", "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ=="], "tarn": ["tarn@3.0.2", "", {}, "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="], @@ -4646,6 +4708,8 @@ "tsscmp": ["tsscmp@1.0.6", "", {}, "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="], + "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], + "tuf-js": ["tuf-js@4.1.0", "", { "dependencies": { "@tufjs/models": "4.1.0", "debug": "^4.4.3", "make-fetch-happen": "^15.0.1" } }, "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ=="], "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], @@ -5238,6 +5302,10 @@ "@kobalte/core/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="], + "@kubernetes/client-node/openid-client": ["openid-client@6.8.2", "", { "dependencies": { "jose": "^6.1.3", "oauth4webapi": "^3.8.4" } }, "sha512-uOvTCndr4udZsKihJ68H9bUICrriHdUVJ6Az+4Ns6cW55rwM5h0bjVIzDz2SxgOI84LKjFyjOFvERLzdTUROGA=="], + + "@kubernetes/client-node/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + "@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], "@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], @@ -5368,6 +5436,8 @@ "@slack/socket-mode/@types/ws": ["@types/ws@7.4.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww=="], + "@slack/socket-mode/eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], + "@slack/socket-mode/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], "@slack/web-api/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="], @@ -5678,6 +5748,8 @@ "p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + "p-queue/eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], + "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], "parse-bmfont-xml/xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], @@ -5776,6 +5848,8 @@ "tree-sitter-bash/node-addon-api": ["node-addon-api@8.7.0", "", {}, "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA=="], + "tsx/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + "tw-to-css/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], "tw-to-css/tailwindcss": ["tailwindcss@3.3.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.18.2", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w=="], @@ -6136,6 +6210,8 @@ "@jsx-email/doiuse-email/htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "@kubernetes/client-node/openid-client/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], + "@malept/flatpak-bundler/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], "@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], @@ -6408,6 +6484,58 @@ "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], + + "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + + "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + + "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + + "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + + "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + + "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + + "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + + "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + + "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + + "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + + "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + + "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + + "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + + "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + + "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + + "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + + "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + + "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + + "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + + "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + + "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + + "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + + "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + + "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + + "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + "tw-to-css/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "tw-to-css/tailwindcss/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], diff --git a/packages/opencode-router-app/.env.local.example b/packages/opencode-router-app/.env.local.example new file mode 100644 index 000000000000..6c3bb3df969b --- /dev/null +++ b/packages/opencode-router-app/.env.local.example @@ -0,0 +1,3 @@ +# No local overrides needed — in dev, open http://localhost:3002 (the router), not http://localhost:5173. +# The router proxies to Vite (DEV_VITE_URL) when no pod is running, and to the pod when running. +# Vite runs as a background HMR server only; its port (5173) is not used directly. diff --git a/packages/opencode-router-app/.gitignore b/packages/opencode-router-app/.gitignore new file mode 100644 index 000000000000..d9a33f4f777f --- /dev/null +++ b/packages/opencode-router-app/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env.local diff --git a/packages/opencode-router-app/index.html b/packages/opencode-router-app/index.html new file mode 100644 index 000000000000..9d757af37407 --- /dev/null +++ b/packages/opencode-router-app/index.html @@ -0,0 +1,13 @@ + + + + + + OpenCode + + + +
+ + + diff --git a/packages/opencode-router-app/package.json b/packages/opencode-router-app/package.json new file mode 100644 index 000000000000..6d1278227be6 --- /dev/null +++ b/packages/opencode-router-app/package.json @@ -0,0 +1,24 @@ +{ + "name": "@opencode-ai/opencode-router-app", + "version": "0.0.1", + "type": "module", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@opencode-ai/ui": "workspace:*", + "@kobalte/core": "catalog:", + "solid-js": "catalog:" + }, + "devDependencies": { + "@tailwindcss/vite": "catalog:", + "@tsconfig/node22": "catalog:", + "tailwindcss": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plugin-solid": "catalog:" + } +} diff --git a/packages/opencode-router-app/src/api.ts b/packages/opencode-router-app/src/api.ts new file mode 100644 index 000000000000..fc3b8e5f179e --- /dev/null +++ b/packages/opencode-router-app/src/api.ts @@ -0,0 +1,50 @@ +export interface Session { + hash: string; + email: string; + repoUrl: string; + branch: string; + state: "creating" | "running"; + url: string; +} + +export interface SessionsResponse { + email: string; + sessions: Session[]; +} + +export async function listSessions(): Promise { + const res = await fetch("/api/sessions"); + if (!res.ok) throw new Error(`Failed to list sessions: ${res.status}`); + return res.json(); +} + +export interface CreateSessionResponse { + hash: string; + url: string; + state: "creating"; + error?: string; +} + +export async function createSession( + repoUrl: string, + branch: string +): Promise { + const res = await fetch("/api/sessions", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ repoUrl, branch }), + }); + if (!res.ok) { + const body = await res.json().catch(() => ({})); + throw new Error(body.error ?? `Failed to create session: ${res.status}`); + } + return res.json(); +} + +export async function getSessionState( + hash: string +): Promise<{ hash: string; state: "creating" | "running"; url: string }> { + const res = await fetch(`/api/sessions/${hash}`); + if (!res.ok) throw new Error(`Failed to get session state: ${res.status}`); + return res.json(); +} diff --git a/packages/opencode-router-app/src/app.tsx b/packages/opencode-router-app/src/app.tsx new file mode 100644 index 000000000000..e4332e5dba59 --- /dev/null +++ b/packages/opencode-router-app/src/app.tsx @@ -0,0 +1,140 @@ +import { Button } from "@opencode-ai/ui/button"; +import { Logo } from "@opencode-ai/ui/logo"; +import { For, Match, Show, Switch, createSignal, onMount } from "solid-js"; +import { type Session, listSessions } from "./api"; +import { LoadingScreen } from "./loading-screen"; +import { SetupForm } from "./setup-form"; + +type Phase = + | { kind: "loading" } + | { kind: "list"; sessions: Session[]; email: string } + | { kind: "new-session"; email: string } + | { kind: "creating"; hash: string; url: string } + | { kind: "error"; message: string }; + +export function App() { + const [phase, setPhase] = createSignal({ kind: "loading" }); + + const loadSessions = async () => { + try { + const { email, sessions } = await listSessions(); + setPhase({ kind: "list", sessions, email }); + } catch (err) { + setPhase({ + kind: "error", + message: err instanceof Error ? err.message : "Failed to connect", + }); + } + }; + + onMount(loadSessions); + + return ( +
+
+ + + + +

+ Loading... +

+
+ + )}> + {(p) => ( +
+
+

+ Signed in as {p().email || "—"} +

+
+ + 0}> + + + + +
+ )} +
+ + ) + } + > + {(p) => ( +
+ + setPhase({ kind: "creating", hash, url })} + /> +
+ )} +
+ + )} + > + {(p) => } + + + )}> + {(p) => ( +

+ {p().message} +

+ )} +
+
+
+
+ ); +} diff --git a/packages/opencode-router-app/src/entry.tsx b/packages/opencode-router-app/src/entry.tsx new file mode 100644 index 000000000000..0b48cb4549ca --- /dev/null +++ b/packages/opencode-router-app/src/entry.tsx @@ -0,0 +1,13 @@ +import { render } from "solid-js/web" +import { ThemeProvider } from "@opencode-ai/ui/theme/context" +import { App } from "./app" +import "./index.css" + +render( + () => ( + + + + ), + document.getElementById("root")!, +) diff --git a/packages/opencode-router-app/src/index.css b/packages/opencode-router-app/src/index.css new file mode 100644 index 000000000000..d5a90532df41 --- /dev/null +++ b/packages/opencode-router-app/src/index.css @@ -0,0 +1,2 @@ +@import "@opencode-ai/ui/styles"; +@import "@opencode-ai/ui/styles/tailwind"; diff --git a/packages/opencode-router-app/src/loading-screen.tsx b/packages/opencode-router-app/src/loading-screen.tsx new file mode 100644 index 000000000000..922b00e2b6f2 --- /dev/null +++ b/packages/opencode-router-app/src/loading-screen.tsx @@ -0,0 +1,40 @@ +import { onCleanup, onMount } from "solid-js"; +import { getSessionState } from "./api"; + +export function LoadingScreen(props: { hash: string; url: string }) { + let timer: ReturnType; + + onMount(() => { + timer = setInterval(async () => { + try { + const session = await getSessionState(props.hash); + if (session.state === "running") { + clearInterval(timer); + window.location.replace(props.url); + } + } catch { + // Retry on next tick + } + }, 3000); + }); + + onCleanup(() => clearInterval(timer)); + + return ( +
+
+

+ Starting your OpenCode session... +

+

+ This usually takes a few seconds. +

+
+ ); +} diff --git a/packages/opencode-router-app/src/setup-form.tsx b/packages/opencode-router-app/src/setup-form.tsx new file mode 100644 index 000000000000..c43a13502605 --- /dev/null +++ b/packages/opencode-router-app/src/setup-form.tsx @@ -0,0 +1,70 @@ +import { Button } from "@opencode-ai/ui/button"; +import { TextField } from "@opencode-ai/ui/text-field"; +import { createSignal } from "solid-js"; +import { createSession } from "./api"; + +const GIT_URL_PATTERN = /^https?:\/\/.+\/.+/; + +export function SetupForm(props: { + email: string; + onCreated: (hash: string, url: string) => void; +}) { + const [repoUrl, setRepoUrl] = createSignal(""); + const [branch, setBranch] = createSignal(""); + const [error, setError] = createSignal(""); + const [submitting, setSubmitting] = createSignal(false); + + const validate = (): string | null => { + const url = repoUrl().trim(); + if (!url) return "Repository URL is required"; + if (!GIT_URL_PATTERN.test(url)) return "Enter a valid HTTP(S) repository URL"; + if (!branch().trim()) return "Branch name is required"; + return null; + }; + + const handleSubmit = async (e: SubmitEvent) => { + e.preventDefault(); + const validationError = validate(); + if (validationError) { + setError(validationError); + return; + } + + setError(""); + setSubmitting(true); + try { + const result = await createSession(repoUrl().trim(), branch().trim()); + props.onCreated(result.hash, result.url); + } catch (err) { + setError(err instanceof Error ? err.message : "Network error"); + } finally { + setSubmitting(false); + } + }; + + return ( +
+
+

+ Signed in as {props.email} +

+
+ + + + + + + + ); +} diff --git a/packages/opencode-router-app/tsconfig.json b/packages/opencode-router-app/tsconfig.json new file mode 100644 index 000000000000..ec302b5df958 --- /dev/null +++ b/packages/opencode-router-app/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "bundler", + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noEmit": true, + "isolatedModules": true, + "resolveJsonModule": true, + "types": ["vite/client"], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] +} diff --git a/packages/opencode-router-app/vite.config.ts b/packages/opencode-router-app/vite.config.ts new file mode 100644 index 000000000000..d05f6f4de52e --- /dev/null +++ b/packages/opencode-router-app/vite.config.ts @@ -0,0 +1,16 @@ +import { fileURLToPath } from "url"; +import tailwindcss from "@tailwindcss/vite"; +import { defineConfig } from "vite"; +import solidPlugin from "vite-plugin-solid"; + +export default defineConfig({ + plugins: [tailwindcss(), solidPlugin()], + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + }, + }, + build: { + target: "esnext", + }, +}); diff --git a/packages/opencode-router/.env.local.example b/packages/opencode-router/.env.local.example new file mode 100644 index 000000000000..8930ed205c7c --- /dev/null +++ b/packages/opencode-router/.env.local.example @@ -0,0 +1,50 @@ +# Local development environment for opencode-router +# Copy to .env.local, then run: pnpm dev (loaded automatically via tsx --env-file) +# +# Prerequisites: +# 1. Generate a temp kubeconfig: ./scripts/create-local-kubeconfig.sh +# 2. Start the SPA dev server: cd ../opencode-router-app && pnpm dev + +# --- Auth bypass (dev only — never set in production) --- +DEV_EMAIL=dev@local.test + +# --- Kubernetes --- +KUBECONFIG=/tmp/opencode-router-local.kubeconfig +OPENCODE_NAMESPACE=opencode-router +# Dev-only: the homelab k3s cluster uses a self-signed cert. Bun's TLS stack does not +# pick up the certificate-authority-data from the kubeconfig the way kubectl does. +# This disables TLS verification for the k8s API calls — safe for local dev only. +NODE_TLS_REJECT_UNAUTHORIZED=0 + +# --- Router config --- +PORT=3002 +OPENCODE_IMAGE=ghcr.io/mrsimpson/opencode:latest +OPENCODE_PORT=4096 +API_KEY_SECRET_NAME=opencode-api-keys +CONFIG_MAP_NAME=opencode-config-dir +IMAGE_PULL_SECRET_NAME=ghcr-pull-secret +STORAGE_CLASS=longhorn-uncritical +STORAGE_SIZE=2Gi + +# The public domain — sessions are served at ://. +# In local dev, use "localhost:3002" — browsers resolve *.localhost to 127.0.0.1 +# natively (no /etc/hosts needed). Session URLs become http://.localhost:3002 +# In production set this to the real domain: opencode-router.no-panic.org +ROUTER_DOMAIN=localhost:3002 + +# Optional: auto-clone a repo for new sessions +# DEFAULT_GIT_REPO=https://github.com/your-org/your-repo.git + +# Optional: idle pod cleanup timeout (default 30 min) +# IDLE_TIMEOUT_MINUTES=30 + +# Dev-only: fixed proxy target for the opencode pod (pod IPs are unreachable outside the cluster). +# Port-forward the user pod first: kubectl port-forward 4096:4096 -n opencode-router +# Then set this so the router proxies through localhost instead of the unreachable pod IP. +DEV_POD_PROXY_TARGET=http://localhost:4096 + +# Dev-only: proxy the setup UI to Vite dev server instead of serving static files. +# This enables HMR and eliminates the redirect loop (router is the single entry point). +# Start Vite with: cd ../opencode-router-app && pnpm dev +# Then open http://localhost:3002 (not localhost:5173) +DEV_VITE_URL=http://localhost:5173 diff --git a/packages/opencode-router/.gitignore b/packages/opencode-router/.gitignore new file mode 100644 index 000000000000..d9a33f4f777f --- /dev/null +++ b/packages/opencode-router/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env.local diff --git a/packages/opencode-router/Dockerfile b/packages/opencode-router/Dockerfile new file mode 100644 index 000000000000..4ce15906bb7a --- /dev/null +++ b/packages/opencode-router/Dockerfile @@ -0,0 +1,81 @@ +# Stage 1: Build the setup UI SPA +FROM oven/bun:1 AS spa-build +WORKDIR /monorepo +COPY package.json bun.lock bunfig.toml ./ +# Copy ALL workspace package.json files so bun can resolve the workspace lockfile +COPY packages/app/package.json packages/app/ +COPY packages/console/app/package.json packages/console/app/ +COPY packages/console/core/package.json packages/console/core/ +COPY packages/console/function/package.json packages/console/function/ +COPY packages/console/mail/package.json packages/console/mail/ +COPY packages/console/resource/package.json packages/console/resource/ +COPY packages/desktop/package.json packages/desktop/ +COPY packages/desktop-electron/package.json packages/desktop-electron/ +COPY packages/enterprise/package.json packages/enterprise/ +COPY packages/function/package.json packages/function/ +COPY packages/opencode/package.json packages/opencode/ +COPY packages/opencode-router/package.json packages/opencode-router/ +COPY packages/opencode-router-app/package.json packages/opencode-router-app/ +COPY packages/plugin/package.json packages/plugin/ +COPY packages/script/package.json packages/script/ +COPY packages/sdk/js/package.json packages/sdk/js/ +COPY packages/slack/package.json packages/slack/ +COPY packages/storybook/package.json packages/storybook/ +COPY packages/ui/package.json packages/ui/ +COPY packages/util/package.json packages/util/ +COPY packages/web/package.json packages/web/ +COPY patches/ patches/ +RUN bun install --frozen-lockfile --ignore-scripts +COPY packages/ui/ packages/ui/ +COPY packages/opencode-router-app/ packages/opencode-router-app/ +RUN bun run --cwd packages/opencode-router-app build + +# Stage 2: Build the router +# Use bun (the monorepo package manager) — no package-lock.json exists +FROM oven/bun:1 AS router-build +WORKDIR /monorepo +COPY package.json bun.lock bunfig.toml ./ +# Copy ALL workspace package.json files so bun can resolve the workspace lockfile +COPY packages/app/package.json packages/app/ +COPY packages/console/app/package.json packages/console/app/ +COPY packages/console/core/package.json packages/console/core/ +COPY packages/console/function/package.json packages/console/function/ +COPY packages/console/mail/package.json packages/console/mail/ +COPY packages/console/resource/package.json packages/console/resource/ +COPY packages/desktop/package.json packages/desktop/ +COPY packages/desktop-electron/package.json packages/desktop-electron/ +COPY packages/enterprise/package.json packages/enterprise/ +COPY packages/function/package.json packages/function/ +COPY packages/opencode/package.json packages/opencode/ +COPY packages/opencode-router/package.json packages/opencode-router/ +COPY packages/opencode-router-app/package.json packages/opencode-router-app/ +COPY packages/plugin/package.json packages/plugin/ +COPY packages/script/package.json packages/script/ +COPY packages/sdk/js/package.json packages/sdk/js/ +COPY packages/slack/package.json packages/slack/ +COPY packages/storybook/package.json packages/storybook/ +COPY packages/ui/package.json packages/ui/ +COPY packages/util/package.json packages/util/ +COPY packages/web/package.json packages/web/ +COPY patches/ patches/ +RUN bun install --frozen-lockfile --ignore-scripts +COPY packages/opencode-router/tsconfig.json packages/opencode-router/ +COPY packages/opencode-router/src/ packages/opencode-router/src/ +RUN bun run --cwd packages/opencode-router build + +# Stage 3: Final image +# Use node:22-alpine for a lean runtime; copy compiled dist + node_modules from build stage +FROM node:22-alpine +WORKDIR /app +# Copy compiled JS output +COPY --from=router-build /monorepo/packages/opencode-router/dist ./dist +# Copy package.json for runtime metadata (type:module) +COPY packages/opencode-router/package.json ./ +# Copy production node_modules from bun's workspace install +COPY --from=router-build /monorepo/node_modules ./node_modules +COPY --from=spa-build /monorepo/packages/opencode-router-app/dist ./public +EXPOSE 3000 +USER 1000 +CMD ["node", "dist/index.js"] + + diff --git a/packages/opencode-router/README.md b/packages/opencode-router/README.md new file mode 100644 index 000000000000..78b889ce7b00 --- /dev/null +++ b/packages/opencode-router/README.md @@ -0,0 +1,120 @@ +# opencode-router + +A lightweight HTTP/WebSocket reverse proxy that provides per-user isolation for [OpenCode](https://opencode.ai) deployments on Kubernetes. + +## Problem + +OpenCode's `opencode serve` command runs a single server instance. When multiple users share that instance, they see each other's sessions, share the same filesystem, and operate under the same API keys. There is no user isolation. + +For teams or shared environments, each user needs their own OpenCode instance with: + +- **Isolated sessions** — users only see their own conversation history +- **Isolated filesystem** — each user works in their own workspace +- **Persistent state** — sessions survive pod restarts and idle cleanup +- **Shared configuration** — API keys and agent configs are managed centrally + +## What This Does + +`opencode-router` sits between an authentication proxy (e.g. oauth2-proxy) and dynamically provisioned OpenCode pods. It: + +1. **Identifies users** via the `X-Auth-Request-Email` header (set by oauth2-proxy after GitHub/Google/OIDC authentication) +2. **Provisions infrastructure** on first request for a (user, repo, branch) triple — creates a PersistentVolumeClaim and a Pod running `opencode serve` +3. **Routes by subdomain** — each session gets a dedicated hostname (`.`); the router extracts the hash from the `Host` header and proxies to the correct pod +4. **Proxies traffic** — forwards all HTTP requests and WebSocket connections to the session's pod +5. **Cleans up idle pods** — deletes pods that have been inactive beyond a configurable threshold, while preserving PVCs so state persists + +``` +┌──────────────┐ ┌──────────────────┐ ┌─────────────────────┐ +│ oauth2-proxy │────▶│ opencode-router │────▶│ opencode-user-a3f2 │ +│ (authn) │ │ (routing/mgmt) │ │ (opencode serve) │ +└──────────────┘ └──────────────────┘ └─────────────────────┘ + │ ┌─────────────────────┐ + └───────────────▶│ opencode-user-b7c1 │ + │ (opencode serve) │ + └─────────────────────┘ +``` + +## How It Works + +### Session-to-Pod Mapping + +Each (email, repoUrl, branch) triple is hashed (SHA-256, first 12 hex chars) to produce a deterministic, DNS-safe identifier. This hash is used for the pod name (`opencode-session-`), PVC name (`opencode-pvc-`), and session hostname (`.`), ensuring a stable mapping across pod restarts. + +### Pod Lifecycle + +| Event | Action | +|---|---| +| First request from a new user | Create PVC + Pod with git-clone init container | +| Request while pod is starting | Return a loading page (auto-refreshes every 3s) | +| Request to a running pod | Proxy HTTP/WebSocket to pod IP | +| Pod idle > threshold | Delete pod (PVC preserved) | +| Returning user after idle cleanup | Recreate pod, reattach existing PVC | + +### Git Repository Initialization + +On first pod creation, an init container (`alpine/git`) clones a configurable default repository into the workspace. A guard (`test -d /workspace/.git`) prevents re-cloning on subsequent pod restarts since the PVC retains the data. + +### What Gets Isolated vs. Shared + +| Resource | Per-User | Shared | +|---|---|---| +| SQLite database (sessions, history) | Per-user PVC | — | +| Workspace / filesystem | Per-user PVC | — | +| API keys (ANTHROPIC_API_KEY, etc.) | — | K8s Secret | +| Agent configuration | — | K8s ConfigMap | +| OpenCode config (opencode.json) | — | K8s ConfigMap | + +## Session URL Pattern + +Each session is identified by a 12-char hex hash of `(email, repoUrl, branch)` and served at a dedicated hostname: + +``` +https://. +``` + +Examples: +- Production (`ROUTE_SUFFIX=-oc`, `ROUTER_DOMAIN=no-panic.org`): `https://abc123def456-oc.no-panic.org` +- Local dev (`ROUTE_SUFFIX=`, `ROUTER_DOMAIN=localhost:3002`): `http://abc123def456.localhost:3002` + +The `-oc` suffix keeps session hostnames at the **first subdomain level**, which is covered by a standard `*.` wildcard TLS certificate. Without the suffix, sessions would be at `..` (second level) which requires a more expensive wildcard certificate. + +## Configuration + +All configuration is via environment variables on the router pod: + +| Variable | Required | Default | Description | +|---|---|---|---| +| `OPENCODE_IMAGE` | Yes | — | Docker image for session OpenCode pods | +| `ROUTER_DOMAIN` | Yes | — | Base domain for session URLs (e.g. `no-panic.org`) | +| `ROUTE_SUFFIX` | No | `""` | Suffix appended to hash in session hostname (e.g. `-oc`) | +| `OPENCODE_NAMESPACE` | No | `opencode` | Kubernetes namespace for all resources | +| `IDLE_TIMEOUT_MINUTES` | No | `30` | Minutes of inactivity before pod deletion | +| `API_KEY_SECRET_NAME` | No | `opencode-api-keys` | K8s Secret name containing API keys | +| `CONFIG_MAP_NAME` | No | `opencode-config-dir` | K8s ConfigMap with shared OpenCode config | +| `STORAGE_CLASS` | No | `""` (cluster default) | StorageClass for session PVCs | +| `STORAGE_SIZE` | No | `2Gi` | PVC size per session | +| `DEFAULT_GIT_REPO` | No | — | Git repo URL to clone into new workspaces | +| `PORT` | No | `3000` | Port the router listens on | + +## Prerequisites + +- Kubernetes cluster with RBAC enabled +- An authentication proxy (oauth2-proxy or similar) that sets `X-Auth-Request-Email` +- A ServiceAccount for the router with permissions to manage Pods and PVCs in its namespace +- The OpenCode Docker image available to the cluster + +## Deployment + +The router runs as a standard Kubernetes Deployment. It requires a ServiceAccount with a Role granting: + +```yaml +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch", "create", "delete", "patch"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list", "create"] +``` + +The router itself is stateless — it discovers user pods via the Kubernetes API on every request. Multiple router replicas can run behind a Service for availability (all replicas see the same pods). diff --git a/packages/opencode-router/docs/adr-001-per-user-pod-isolation.md b/packages/opencode-router/docs/adr-001-per-user-pod-isolation.md new file mode 100644 index 000000000000..3bd6bf8770c4 --- /dev/null +++ b/packages/opencode-router/docs/adr-001-per-user-pod-isolation.md @@ -0,0 +1,164 @@ +# ADR-001: Per-User Pod Isolation via Reverse Proxy Router + +**Status:** Accepted +**Date:** 2026-04-02 + +## Context + +OpenCode provides a web-based AI coding assistant via `opencode serve`. A single instance stores all state (sessions, conversation history, file system) in a local SQLite database and the local filesystem. There is no built-in concept of user accounts or multi-tenancy. + +When deploying OpenCode for a team behind an authentication proxy (e.g. oauth2-proxy), all authenticated users currently share a single instance. This means: + +- Users can see each other's sessions and conversation history +- File system changes from one user affect all others +- There is no way to attribute API usage to individual users +- A misbehaving session can degrade the experience for everyone + +We need per-user isolation without modifying OpenCode's core codebase. + +## Decision + +We will create a **reverse proxy router** (`opencode-router`) that dynamically provisions a separate Kubernetes Pod for each authenticated user. The router: + +1. Reads the user's identity from the `X-Auth-Request-Email` HTTP header (set by the upstream auth proxy) +2. Maps each user to a deterministic Pod and PVC via a SHA-256 hash of their email +3. Creates the Pod and PVC on first access +4. Proxies all subsequent HTTP and WebSocket traffic to the user's Pod +5. Deletes idle Pods after a configurable timeout, preserving PVCs + +### Alternatives Considered + +#### A. Modify OpenCode to support multi-tenancy natively + +**Rejected.** This would require significant changes to OpenCode's data layer (multi-tenant SQLite or migration to a shared database), session management, and file system isolation. It couples the isolation concern to the application, making it harder to maintain across upstream updates. The router approach achieves the same goal with zero changes to OpenCode. + +#### B. One Deployment per user (static provisioning) + +**Rejected.** Pre-provisioning a Deployment for each user requires manual intervention for onboarding/offboarding and wastes resources for inactive users. Dynamic provisioning scales to zero when idle and handles new users automatically. + +#### C. Namespace-per-user isolation + +**Rejected.** While providing stronger isolation boundaries, namespace-per-user significantly complicates RBAC management, resource quota configuration, and ConfigMap/Secret distribution. Pod-level isolation within a single namespace is sufficient for this use case — users are authenticated team members, not untrusted tenants. + +#### D. Virtual clusters (vCluster) or container-in-container (sysbox) + +**Rejected.** These provide stronger isolation but add operational complexity disproportionate to the threat model. Users are authenticated via the organization's identity provider; the goal is session/data separation, not security sandboxing against malicious users. + +## Architecture + +``` +Internet + │ + ▼ +┌──────────────┐ +│ Ingress │ +│ (Traefik) │ +└──────┬───────┘ + │ + ▼ +┌──────────────┐ X-Auth-Request-Email +│ oauth2-proxy │ ─────────────────────▶ +│ (ForwardAuth│ +│ middleware)│ +└──────┬───────┘ + │ + ▼ +┌──────────────────┐ +│ opencode-router │──┐ +│ (this service) │ │ +└──────────────────┘ │ + │ │ + ▼ ▼ +┌────────────┐ ┌────────────┐ +│ user-pod-A │ │ user-pod-B │ ... +│ (opencode │ │ (opencode │ +│ serve) │ │ serve) │ +├────────────┤ ├────────────┤ +│ PVC-A │ │ PVC-B │ +└────────────┘ └────────────┘ +``` + +## Design Decisions + +### D1: Deterministic pod naming via email hash + +**Decision:** Pod names are `opencode-user-`. + +**Rationale:** This is deterministic (same email always maps to same pod), DNS-safe (hex chars only), and reasonably collision-resistant (12 hex chars = 48 bits, sufficient for team-scale deployments of hundreds of users). No external state or database is needed to track the mapping. + +**Trade-off:** If two emails collide on the first 12 chars of their SHA-256 hash, they would share a pod. At team scale (< 1000 users), this probability is negligible (~10^-9). For deployments exceeding thousands of users, the hash prefix length should be increased. + +### D2: PVCs survive pod deletion + +**Decision:** Idle cleanup deletes Pods but preserves PersistentVolumeClaims. + +**Rationale:** The primary cost of idle pods is compute (CPU/memory), not storage. Preserving PVCs means returning users get their full session history, conversation logs, and workspace files without re-cloning. Storage costs for 2Gi PVCs are minimal. Manual PVC cleanup can be done via `kubectl` or a separate job for offboarded users. + +**Trade-off:** Orphaned PVCs accumulate for users who never return. A future enhancement could add a separate PVC cleanup job with a longer timeout (e.g. 30 days). + +### D3: In-cluster Kubernetes API access + +**Decision:** The router uses the in-cluster service account for Kubernetes API access (`@kubernetes/client-node` with `KubeConfig.loadFromCluster()`). + +**Rationale:** The router runs inside the cluster it manages. In-cluster auth is the standard, secure approach — no kubeconfig files to manage, credentials rotate automatically, and RBAC scopes permissions precisely. + +### D4: http-proxy for proxying + +**Decision:** Use the `http-proxy` npm package for HTTP and WebSocket proxying. + +**Rationale:** `http-proxy` is battle-tested (380M+ weekly downloads), supports WebSocket upgrades natively, and is simple to integrate with Node.js `http.Server`. OpenCode's web UI relies on WebSocket connections for real-time updates and terminal PTY, making native WS support essential. No framework (Express, Fastify) is needed — the router's logic is simple enough for raw `http.createServer`. + +**Alternatives considered:** +- `node-http-proxy-json` — unnecessary, we don't need response body manipulation +- nginx/envoy sidecar — adds operational complexity for dynamic upstream management +- Custom proxy with `fetch` — lacks WebSocket support without significant additional code + +### D5: Node.js runtime (not Bun) + +**Decision:** The router targets Node.js 22, not Bun. + +**Rationale:** `@kubernetes/client-node` is tested and maintained against Node.js. While Bun has broad Node.js compatibility, the Kubernetes client uses features (HTTP/2, specific TLS behaviors) where Node.js compatibility is battle-proven. The router is I/O-bound (proxying requests, K8s API calls), so Bun's performance advantages in compute-heavy workloads don't apply. Using Node.js avoids an unnecessary compatibility risk. + +### D6: Loading page with client-side refresh + +**Decision:** When a user's pod is not yet ready, return an HTML page that auto-refreshes every 3 seconds. + +**Rationale:** This is the simplest approach that works. Pod startup (pull image + init container + readiness) takes 5-15 seconds. Server-Sent Events or long-polling would add complexity for marginal UX improvement. The loading page is self-contained HTML with no external dependencies. + +**Trade-off:** Users see a brief flash on each refresh until the pod is ready. For a more polished experience, a future iteration could use SSE to push a redirect once the pod is ready. + +### D7: Activity tracking via pod annotations + +**Decision:** Last activity time is stored as a Kubernetes annotation (`opencode.ai/last-activity`) on the user's pod, updated on each proxied request. + +**Rationale:** This avoids external state (no Redis, no database). Annotations are lightweight metadata on existing resources. The cleanup loop lists pods with the `opencode.ai/user-hash` label and checks annotations. Since the router is the only writer, there are no concurrency concerns. + +**Trade-off:** Frequent annotation updates (every request) generate K8s API load. To mitigate this, the router throttles updates to at most once per minute per user (in-memory timestamp cache, write only when >60s since last update). + +### D8: Single namespace for all resources + +**Decision:** The router, user pods, PVCs, ConfigMaps, and Secrets all reside in a single Kubernetes namespace. + +**Rationale:** Simplifies RBAC (one Role, one RoleBinding), ConfigMap/Secret sharing (no cross-namespace references), and operational visibility (`kubectl get pods -n opencode` shows everything). The isolation boundary is at the pod/PVC level, not the namespace level. + +## Consequences + +### Positive +- Zero changes to OpenCode — works with any version of `opencode serve` +- Users get full isolation of sessions, history, and workspace +- Idle cleanup keeps compute costs proportional to active users +- PVC persistence means no data loss on pod recycling +- Stateless router can be scaled horizontally for availability +- New users are onboarded automatically on first visit + +### Negative +- Each active user consumes a full pod's worth of resources (CPU, memory) +- Pod startup latency on first visit or after idle cleanup (5-15 seconds) +- Orphaned PVCs require manual or automated cleanup for offboarded users +- Router adds a network hop and a single point of failure (mitigated by replicas) +- K8s API rate limits could be reached with very frequent annotation updates (mitigated by throttling) + +### Risks +- **K8s API availability:** If the Kubernetes API is down, the router cannot create or discover pods. Mitigation: the router should cache known pod IPs in memory as a short-term fallback. +- **Resource exhaustion:** A large number of concurrent users could exhaust namespace resource quotas. Mitigation: configure `LimitRange` and `ResourceQuota` on the namespace. +- **Image pull latency:** First pod creation requires pulling the OpenCode image. Mitigation: use `imagePullPolicy: IfNotPresent` and pre-pull images on nodes via a DaemonSet. diff --git a/packages/opencode-router/docs/adr-002-restricted-pss-securitycontext.md b/packages/opencode-router/docs/adr-002-restricted-pss-securitycontext.md new file mode 100644 index 000000000000..3af3fd01278f --- /dev/null +++ b/packages/opencode-router/docs/adr-002-restricted-pss-securitycontext.md @@ -0,0 +1,93 @@ +# ADR-002: Restricted PSS SecurityContext for User Pods + +**Status:** Accepted +**Date:** 2026-04-03 + +## Context + +The `opencode-router` dynamically creates Kubernetes Pods for each authenticated user (see ADR-001). The original `pod-manager.ts` implementation created these pods without any `securityContext`, meaning: + +- Pods ran as whatever user the container image specified (typically root) +- No privilege escalation restrictions were in place +- Linux capabilities were unrestricted +- The namespace Pod Security Standards (PSS) level could not be set to `restricted` +- PVCs mounted at `/root` would be owned by root (UID 0), preventing write access by UID 1000 + +When deploying to a homelab Kubernetes cluster that enforces PSS `restricted` at the namespace level, pods without securityContext would be rejected by the admission controller. + +Additionally, the OpenCode container image used in this deployment (`ghcr.io/mrsimpson/opencode`) runs the `opencode` binary as UID 1000. Without `fsGroup: 1000` in the pod-level securityContext, a PVC mounted at `/root` would be owned by root and the opencode process could not write to it. + +## Decision + +We add explicit `securityContext` blocks to all containers in the user pod spec (in `pod-manager.ts`): + +### Pod-level securityContext + +```typescript +securityContext: { + runAsUser: 1000, + runAsGroup: 1000, + fsGroup: 1000, // Makes PVC owned by GID 1000 on mount + runAsNonRoot: true, +} +``` + +### Main container (`opencode`) securityContext + +```typescript +securityContext: { + allowPrivilegeEscalation: false, + runAsNonRoot: true, + capabilities: { drop: ["ALL"] }, + seccompProfile: { type: "RuntimeDefault" }, +} +``` + +### Init container (`git-init`) securityContext + +```typescript +securityContext: { + runAsUser: 1000, + runAsGroup: 1000, + allowPrivilegeEscalation: false, + runAsNonRoot: true, + capabilities: { drop: ["ALL"] }, +} +``` + +The `git-init` container uses `alpine/git` which does not have a pre-built seccomp profile; `RuntimeDefault` is not added to avoid compatibility issues with the Alpine image. + +## Alternatives Considered + +### A. Use namespace PSS `privileged` or `baseline` + +**Rejected.** Weaker PSS levels allow privilege escalation and do not enforce non-root execution. This is unnecessary for workloads that are fully capable of running as non-root. `restricted` is the secure default; relaxing it should require a strong justification. + +### B. Use an init container to `chown` the PVC after mount + +```dockerfile +# init container runs as root, chowns /root to 1000:1000, then main container runs as 1000 +``` + +**Rejected.** This requires the init container to run as root (`runAsUser: 0`), which violates `restricted` PSS. Using `fsGroup: 1000` achieves the same result (PVC accessible by GID 1000) via the kubelet without requiring a privileged init container. + +### C. Build the image to run as root, handle permissions at runtime + +**Rejected.** Running as root in production violates least-privilege principles and fails `restricted` PSS. The homelab image (`ghcr.io/mrsimpson/opencode`) already runs as UID 1000; this is the correct foundation to build on. + +## Consequences + +### Positive +- User pods are fully compliant with Kubernetes PSS `restricted` level +- The `opencode-router` namespace can be labeled `pod-security.kubernetes.io/enforce: restricted` +- PVCs mounted at `/root` are writable by UID 1000 via `fsGroup: 1000` +- No Linux capabilities are granted to user pods +- Seccomp profile `RuntimeDefault` limits syscall surface for the main container +- All containers run as non-root (UID 1000), matching the homelab image + +### Negative +- The `alpine/git` init container must also run as UID 1000. If the git remote requires SSH keys (which are typically root-owned), additional setup may be needed. For HTTPS git clones (the primary use case), UID 1000 is sufficient. +- The `RuntimeDefault` seccomp profile is not applied to `git-init` (Alpine compatibility); a future improvement could pin a specific seccomp profile. + +### Risks +- **Alpine/git UID 1000:** The `alpine/git` image has a user with UID 1000 (`git` user on some versions). If the image creates files as a different UID internally before the securityContext takes effect, this could cause issues. Mitigation: test with the specific `alpine/git:latest` tag; switch to a digest-pinned tag if behavior is inconsistent. diff --git a/packages/opencode-router/docs/deployment.md b/packages/opencode-router/docs/deployment.md new file mode 100644 index 000000000000..7791b4216187 --- /dev/null +++ b/packages/opencode-router/docs/deployment.md @@ -0,0 +1,628 @@ +# Deploying opencode-router on Kubernetes + +This guide describes everything needed to deploy the opencode-router and its setup UI on a Kubernetes cluster. It is written for an agent or engineer translating this into infrastructure-as-code (Pulumi, Terraform, Helm, raw manifests, etc.). + +## System Overview + +The opencode-router provides per-user isolated OpenCode instances on Kubernetes. The full request path is: + +``` +Internet → Ingress → oauth2-proxy (authentication) → opencode-router → per-user Pod +``` + +The router is a stateless Node.js process that: +- Reads the authenticated user's email from the `X-Auth-Request-Email` header +- Serves a setup UI (SolidJS SPA) on first visit so the user can pick a git repo to clone +- Provisions a PersistentVolumeClaim and Pod per user via the Kubernetes API +- Proxies all HTTP and WebSocket traffic to the user's running Pod +- Deletes idle Pods after a timeout (PVCs are preserved) + +The router image includes the pre-built SPA in `/app/public/`. There is one Docker image for both the router and UI — no separate deployment for the frontend. + +## Prerequisites + +1. **A Kubernetes cluster** (1.26+) with a working StorageClass for dynamic PVC provisioning +2. **An Ingress controller** (Traefik, nginx-ingress, etc.) with TLS +3. **An authentication proxy** — oauth2-proxy or equivalent that: + - Authenticates users via GitHub, Google, OIDC, etc. + - Sets the `X-Auth-Request-Email` header on every request forwarded to the backend +4. **The OpenCode Docker image** — the image that runs `opencode serve` (the application being proxied to) +5. **DNS** — a domain name pointing to the Ingress (e.g. `opencode.example.com`) + +## Namespace + +Create a dedicated namespace. All resources (router, user pods, PVCs, secrets, configmaps) live in this single namespace. + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: opencode +``` + +All resources below assume `namespace: opencode`. The router defaults to this name but it is configurable via `OPENCODE_NAMESPACE`. + +## 1. RBAC — ServiceAccount, Role, RoleBinding + +The router runs as a Pod that calls the Kubernetes API to manage user Pods and PVCs. It needs a ServiceAccount with a Role scoped to its namespace. + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: opencode-router + namespace: opencode +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: opencode-router + namespace: opencode +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "create", "delete", "patch"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list", "create"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: opencode-router + namespace: opencode +subjects: + - kind: ServiceAccount + name: opencode-router + namespace: opencode +roleRef: + kind: Role + name: opencode-router + apiGroup: rbac.authorization.k8s.io +``` + +**Why these exact permissions:** +- `pods: get` — check if a user's pod exists and read its IP +- `pods: list` — enumerate all managed pods for idle cleanup (filtered by label `app.kubernetes.io/managed-by=opencode-router`) +- `pods: create` — provision new user pods +- `pods: delete` — delete idle pods during cleanup +- `pods: patch` — update the `opencode.ai/last-activity` annotation on each proxied request (throttled to once/minute/user) +- `persistentvolumeclaims: get` — check if a user's PVC exists before creating +- `persistentvolumeclaims: create` — provision PVCs for new users +- `persistentvolumeclaims: list` — not strictly required currently but included for operational tooling + +The Role is namespace-scoped (not a ClusterRole) — the router can only manage resources in its own namespace. + +## 2. Secret — API Keys + +The router injects API keys into every user Pod via `envFrom: secretRef`. Each key in the Secret becomes an environment variable in the user Pod. + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: opencode-api-keys + namespace: opencode +type: Opaque +stringData: + ANTHROPIC_API_KEY: "sk-ant-..." + # Add any other provider keys the opencode instance needs: + # OPENAI_API_KEY: "sk-..." + # GOOGLE_API_KEY: "..." +``` + +**The Secret name is configurable** via the router's `API_KEY_SECRET_NAME` env var (default: `opencode-api-keys`). The name in the Secret metadata must match. + +**Security note:** Every user Pod gets the same API keys. The router does not support per-user API keys — all users share the organization's keys. If per-user keys are needed, OpenCode's own auth/key configuration would need to handle that. + +## 3. ConfigMap — Shared OpenCode Configuration + +OpenCode reads its configuration from `~/.opencode/`. The router mounts a ConfigMap at `/root/.opencode` (read-only) in every user Pod, so all users share the same agent configuration. + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: opencode-config-dir + namespace: opencode +data: + opencode.json: | + { + "model": { + "default": "claude-sonnet-4-20250514" + } + } +``` + +**The ConfigMap name is configurable** via `CONFIG_MAP_NAME` (default: `opencode-config-dir`). + +The keys in the ConfigMap become files in `/root/.opencode/`. At minimum, `opencode.json` should be present. Add any other config files OpenCode expects (e.g. custom agent instructions). + +**Note:** The ConfigMap is mounted read-only. OpenCode will read this config but cannot modify it. Per-user config overrides would need to live on the user's PVC. + +## 4. Router Deployment + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: opencode-router + namespace: opencode +spec: + replicas: 2 + selector: + matchLabels: + app: opencode-router + template: + metadata: + labels: + app: opencode-router + spec: + serviceAccountName: opencode-router + containers: + - name: router + image: /opencode-router:latest # See "Building the Image" below + ports: + - containerPort: 3000 + env: + - name: OPENCODE_IMAGE + value: "/opencode:latest" + # Optional overrides (showing defaults): + # - name: OPENCODE_NAMESPACE + # value: "opencode" + # - name: PORT + # value: "3000" + # - name: IDLE_TIMEOUT_MINUTES + # value: "30" + # - name: API_KEY_SECRET_NAME + # value: "opencode-api-keys" + # - name: CONFIG_MAP_NAME + # value: "opencode-config-dir" + # - name: STORAGE_CLASS + # value: "" # empty = cluster default StorageClass + # - name: STORAGE_SIZE + # value: "2Gi" + # - name: DEFAULT_GIT_REPO + # value: "" # if set, users skip the repo selection UI + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi + readinessProbe: + httpGet: + path: /api/status + port: 3000 + httpHeaders: + - name: X-Auth-Request-Email + value: healthcheck@probe + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /api/status + port: 3000 + httpHeaders: + - name: X-Auth-Request-Email + value: healthcheck@probe + initialDelaySeconds: 10 + periodSeconds: 30 +``` + +### Environment Variables Reference + +| Variable | Required | Default | Purpose | +|---|---|---|---| +| `OPENCODE_IMAGE` | **Yes** | — | Docker image for user Pods (must be pullable by the cluster) | +| `OPENCODE_NAMESPACE` | No | `opencode` | Namespace where user Pods and PVCs are created | +| `PORT` | No | `3000` | Port the router listens on | +| `IDLE_TIMEOUT_MINUTES` | No | `30` | Minutes of inactivity before a user's Pod is deleted | +| `API_KEY_SECRET_NAME` | No | `opencode-api-keys` | Name of the Secret injected into user Pods | +| `CONFIG_MAP_NAME` | No | `opencode-config-dir` | Name of the ConfigMap mounted as `/root/.opencode` | +| `STORAGE_CLASS` | No | `""` (cluster default) | StorageClass for user PVCs | +| `STORAGE_SIZE` | No | `2Gi` | Storage capacity per user PVC | +| `DEFAULT_GIT_REPO` | No | — | If set, all users get this repo auto-cloned; the setup UI is skipped | +| `PUBLIC_DIR` | No | `./public` | Path to the SPA assets directory (set automatically in the Docker image) | + +### Why 2 replicas + +The router is stateless — it discovers user Pods via the K8s API on every request. Multiple replicas provide availability. The only in-memory state is a throttle cache for activity annotations (once/min/user), which is non-critical — if a different replica handles the next request, it just writes the annotation slightly more often. No sticky sessions are needed. + +### Resource sizing + +The router is lightweight: it proxies requests and makes K8s API calls. 128Mi memory is sufficient for hundreds of concurrent users. The per-user Pods (running `opencode serve`) are the primary resource consumers — size those according to the workload. + +## 5. Service + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: opencode-router + namespace: opencode +spec: + selector: + app: opencode-router + ports: + - port: 80 + targetPort: 3000 + protocol: TCP +``` + +This Service is the target for the Ingress/auth proxy. It does not need to be `type: LoadBalancer` — the Ingress controller handles external exposure. + +## 6. Ingress + Authentication + +The exact configuration depends on your Ingress controller and auth proxy. Below is a Traefik example using oauth2-proxy as ForwardAuth middleware. Adapt for nginx-ingress or other setups. + +### oauth2-proxy Deployment (example) + +oauth2-proxy handles authentication (GitHub, Google, OIDC, etc.) and sets the `X-Auth-Request-Email` header on authenticated requests. + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: oauth2-proxy + namespace: opencode +spec: + replicas: 1 + selector: + matchLabels: + app: oauth2-proxy + template: + metadata: + labels: + app: oauth2-proxy + spec: + containers: + - name: oauth2-proxy + image: quay.io/oauth2-proxy/oauth2-proxy:v7.7.1 + args: + - --provider=github # or google, oidc, etc. + - --email-domain=* # restrict to your org's domain if desired + - --upstream=http://opencode-router.opencode.svc.cluster.local + - --http-address=0.0.0.0:4180 + - --set-xauthrequest=true # CRITICAL: sets X-Auth-Request-Email header + - --pass-access-token=false + - --cookie-secure=true + - --cookie-name=_opencode_oauth + env: + - name: OAUTH2_PROXY_CLIENT_ID + valueFrom: + secretKeyRef: + name: oauth2-proxy + key: client-id + - name: OAUTH2_PROXY_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: oauth2-proxy + key: client-secret + - name: OAUTH2_PROXY_COOKIE_SECRET + valueFrom: + secretKeyRef: + name: oauth2-proxy + key: cookie-secret + ports: + - containerPort: 4180 +--- +apiVersion: v1 +kind: Service +metadata: + name: oauth2-proxy + namespace: opencode +spec: + selector: + app: oauth2-proxy + ports: + - port: 4180 + targetPort: 4180 +``` + +**Important oauth2-proxy config:** +- `--set-xauthrequest=true` is required — this makes oauth2-proxy set the `X-Auth-Request-Email` header that the router reads +- `--upstream` points to the router Service +- Configure `--github-org` or `--email-domain` to restrict access to your organization + +### Traefik ForwardAuth + IngressRoute (example) + +```yaml +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: oauth2-proxy + namespace: opencode +spec: + forwardAuth: + address: http://oauth2-proxy.opencode.svc.cluster.local:4180/oauth2/auth + trustForwardHeader: true + authResponseHeaders: + - X-Auth-Request-Email +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: opencode + namespace: opencode +spec: + entryPoints: + - websecure + routes: + - match: Host(`opencode.example.com`) + kind: Rule + middlewares: + - name: oauth2-proxy + services: + - name: opencode-router + port: 80 + tls: + certResolver: letsencrypt # or your TLS configuration +``` + +### nginx-ingress alternative + +If using nginx-ingress instead of Traefik: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: opencode + namespace: opencode + annotations: + nginx.ingress.kubernetes.io/auth-url: "http://oauth2-proxy.opencode.svc.cluster.local:4180/oauth2/auth" + nginx.ingress.kubernetes.io/auth-response-headers: "X-Auth-Request-Email" + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + # WebSocket support: + nginx.ingress.kubernetes.io/proxy-http-version: "1.1" + nginx.ingress.kubernetes.io/configuration-snippet: | + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; +spec: + ingressClassName: nginx + tls: + - hosts: + - opencode.example.com + secretName: opencode-tls + rules: + - host: opencode.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: opencode-router + port: + number: 80 +``` + +**WebSocket support is critical.** OpenCode uses WebSockets for real-time UI updates and terminal PTY. The Ingress must: +- Support WebSocket upgrade (`Connection: Upgrade`) +- Have long read/send timeouts (at least 3600s) to keep WebSocket connections alive +- Forward the `X-Auth-Request-Email` header through the upgrade + +## 7. Building the Docker Image + +The router image is built from the monorepo root using the Dockerfile at `packages/opencode-router/Dockerfile`. It is a multi-stage build: + +1. **Stage 1 (SPA build):** Uses `oven/bun:1` to install monorepo dependencies and run `vite build` on `packages/opencode-router-app/`. This produces the static SPA assets in `dist/`. +2. **Stage 2 (Router build):** Uses `node:22-alpine` to `npm ci` and `tsc` the router TypeScript. +3. **Stage 3 (Final):** Copies the compiled router (`dist/`), the built SPA (`public/`), and production-only `node_modules` into a minimal `node:22-alpine` image. + +```bash +# Build from the monorepo root: +docker build -f packages/opencode-router/Dockerfile -t opencode-router:latest . +``` + +The build context must be the monorepo root (`.`) because stage 1 needs `packages/ui/` and `packages/opencode-router-app/` from the workspace. + +Push to your registry: +```bash +docker tag opencode-router:latest /opencode-router:latest +docker push /opencode-router:latest +``` + +## What the Router Creates Dynamically + +Understanding what the router creates at runtime is essential for capacity planning and debugging. The router creates these Kubernetes resources via the API — they are not defined in static manifests. + +### Per-User PVCs + +Created on first visit. **Never deleted by the router** — they persist across pod restarts and idle cleanup. + +``` +Name: opencode-pvc- # hash = sha256(email)[0:12] +Namespace: opencode +AccessModes: ReadWriteOnce +StorageClass: +Capacity: +Labels: + opencode.ai/user-hash: + app.kubernetes.io/managed-by: opencode-router +``` + +### Per-User Pods + +Created when a user submits the setup form (or on first visit if `DEFAULT_GIT_REPO` is set). Deleted by the idle cleanup loop. + +``` +Name: opencode-user- +Namespace: opencode +Labels: + opencode.ai/user-hash: + app.kubernetes.io/managed-by: opencode-router +Annotations: + opencode.ai/last-activity: + opencode.ai/user-email: + +Init containers (if git repo configured): + git-init: + image: alpine/git:latest + command: if [ ! -d /workspace/.git ]; then git clone /workspace; fi + volumeMounts: user-data → /workspace (subPath: projects) + +Containers: + opencode: + image: + command: opencode serve --hostname 0.0.0.0 --port 4096 + ports: 4096 + envFrom: secret/opencode-api-keys + volumeMounts: + user-data → /root + opencode-config → /root/.opencode (readOnly) + +Volumes: + user-data: PVC opencode-pvc- + opencode-config: ConfigMap opencode-config-dir +``` + +**Important:** The Pods are bare Pods, not Deployments or StatefulSets. The router manages their lifecycle directly. If a Pod crashes (`restartPolicy: Always` handles container restarts), but if the node goes down, the Pod is gone and the router will recreate it on the next request. This is intentional — the PVC retains all state. + +## Capacity Planning + +| Resource | Per-User | Shared | Notes | +|---|---|---|---| +| Pod | 1 per active user | — | Deleted after idle timeout | +| PVC | 1 per user (ever) | — | Never auto-deleted, survives pod restarts | +| CPU/Memory | Per `opencode serve` | — | Size based on OpenCode workload | +| Storage | `STORAGE_SIZE` per user | — | Default 2Gi; holds sessions, history, workspace | +| Router replicas | — | 2+ recommended | Stateless, handles all users | + +**Resource Quotas:** Consider setting a `ResourceQuota` on the namespace to cap the total number of Pods and PVC storage. The router does not enforce user limits — it will keep creating Pods until K8s rejects the API call. + +**LimitRange:** Consider a `LimitRange` to set default resource requests/limits on user Pods. The router currently does not set resource requests/limits on user Pods — this is a known gap that should be addressed before production use. + +## Network Policies (Optional) + +If your cluster enforces NetworkPolicies, you need to allow: + +1. **Ingress → oauth2-proxy → router:** Standard HTTP/HTTPS +2. **Router → user Pods (port 4096):** The proxy traffic +3. **Router → Kubernetes API server:** For pod/PVC management +4. **User Pods → Internet:** For OpenCode to reach LLM APIs (Anthropic, OpenAI, etc.) +5. **User Pods → git hosts:** For the git-init container to clone repos + +## Operational Notes + +### Monitoring + +- **Router health:** The `/api/status` endpoint (with any email header) returns 200 if the router is running. Use it for readiness/liveness probes. +- **Active users:** `kubectl get pods -n opencode -l app.kubernetes.io/managed-by=opencode-router` lists all user Pods. +- **Idle timestamps:** `kubectl get pods -n opencode -o json | jq '.items[].metadata.annotations["opencode.ai/last-activity"]'` shows last activity per user. + +### Manual PVC Cleanup + +The router never deletes PVCs. To clean up storage for offboarded users: + +```bash +# List all PVCs managed by the router +kubectl get pvc -n opencode -l app.kubernetes.io/managed-by=opencode-router + +# Delete a specific user's PVC (make sure their pod is deleted first) +kubectl delete pvc opencode-pvc- -n opencode +``` + +### Forcing a User Pod Restart + +```bash +# Delete the pod — the router will recreate it on the user's next request +kubectl delete pod opencode-user- -n opencode +``` + +The PVC is preserved, so the user's sessions and files survive. + +### Updating the OpenCode Image + +1. Push a new `OPENCODE_IMAGE` to your registry +2. Update the `OPENCODE_IMAGE` env var on the router Deployment (or keep `:latest` and update the image) +3. Existing user Pods continue running the old image until they are deleted (idle timeout or manual) +4. New Pods (from new users or after idle cleanup) get the new image + +There is no rolling update of user Pods — they are recreated, not updated in place. + +## Complete Deployment Checklist + +1. [ ] Create the `opencode` namespace +2. [ ] Create the ServiceAccount, Role, and RoleBinding +3. [ ] Create the Secret with API keys +4. [ ] Create the ConfigMap with OpenCode configuration +5. [ ] Build and push the router Docker image +6. [ ] Ensure the OpenCode image is available to the cluster +7. [ ] Deploy oauth2-proxy (or your auth proxy) with `--set-xauthrequest=true` +8. [ ] Deploy the router Deployment and Service +9. [ ] Configure Ingress with auth middleware and WebSocket support +10. [ ] Set up DNS for your domain +11. [ ] Test: visit the domain, authenticate, see the repo selection UI, submit, wait for pod, use OpenCode +12. [ ] (Optional) Configure ResourceQuota and LimitRange on the namespace +13. [ ] (Optional) Configure NetworkPolicies +14. [ ] (Optional) Set up a CronJob or script for periodic PVC cleanup + +## Local Development + +To develop and test the router locally against a live cluster (without building and pushing a Docker image): + +### 1. Generate a temporary kubeconfig + +```bash +# Requires: kubectl pointing at the cluster, opencode-router SA already deployed +./scripts/create-local-kubeconfig.sh +``` + +This creates `/tmp/opencode-router-local.kubeconfig` using a short-lived ServiceAccount token (valid 24h by default). + +### 2. Configure local environment + +```bash +cp .env.local.example .env.local +# Edit .env.local if needed (defaults work for the homelab setup) +``` + +### 3. Build and start the router + +```bash +bun run build # compile TypeScript → dist/ +npm run dev # sources .env.local automatically, starts on PORT=3002 +``` + +### 4. Start the SPA dev server (separate terminal) + +```bash +cd ../opencode-router-app +bun run dev # Vite runs on :5173 as a background HMR server +``` + +**Open `http://localhost:3002`** — the router is the single entry point: +- When no pod is running: router proxies the SPA from Vite (`DEV_VITE_URL=http://localhost:5173`), HMR works +- When pod is running: router proxies to the opencode pod via port-forward (`DEV_POD_PROXY_TARGET`) +- `window.location.replace("/")` in the SPA stays at `localhost:3002`, no redirect loop + +### 5. Port-forward the user pod (when testing the running state) + +```bash +./scripts/port-forward-pod.sh # reads DEV_EMAIL from .env.local, computes pod name +``` + +This forwards `localhost:4096 → :4096`. Required because pod IPs are cluster-internal. + +### Auth bypass in local dev + +The router requires `X-Auth-Request-Email` header (set by oauth2-proxy in production). Locally, set `DEV_EMAIL` in `.env.local`: + +``` +export DEV_EMAIL=dev@local.test +``` + +When `DEV_EMAIL` is set and the header is absent, the router assumes that identity. **This env var is never set in production** — the 401 behavior for unauthenticated requests is preserved in all deployed environments. + +### Dev environment variables summary + +| Variable | Where | Purpose | +|---|---|---| +| `DEV_EMAIL` | router `.env.local` | Auth identity when oauth2-proxy header is absent | +| `DEV_VITE_URL` | router `.env.local` | Proxy setup UI to Vite dev server (enables HMR) | +| `DEV_POD_PROXY_TARGET` | router `.env.local` | Fixed proxy target for pod (bypasses unreachable pod IP) | +| `KUBECONFIG` | router `.env.local` | Temp kubeconfig with SA token for cluster access | diff --git a/packages/opencode-router/package.json b/packages/opencode-router/package.json new file mode 100644 index 000000000000..68c43c6d919b --- /dev/null +++ b/packages/opencode-router/package.json @@ -0,0 +1,22 @@ +{ + "name": "@opencode-ai/opencode-router", + "version": "0.0.1", + "type": "module", + "private": true, + "scripts": { + "build": "tsc", + "typecheck": "tsc --noEmit", + "start": "node dist/index.js", + "dev": "bun --env-file=.env.local --watch src/index.ts" + }, + "dependencies": { + "@kubernetes/client-node": "^1.0.0", + "http-proxy": "^1.18.1" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.0", + "@types/http-proxy": "^1.17.16", + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + } +} diff --git a/packages/opencode-router/pnpm-lock.yaml b/packages/opencode-router/pnpm-lock.yaml new file mode 100644 index 000000000000..3a2b6afe6266 --- /dev/null +++ b/packages/opencode-router/pnpm-lock.yaml @@ -0,0 +1,699 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@kubernetes/client-node': + specifier: ^1.0.0 + version: 1.4.0 + http-proxy: + specifier: ^1.18.1 + version: 1.18.1 + devDependencies: + '@tsconfig/node22': + specifier: ^22.0.0 + version: 22.0.5 + '@types/http-proxy': + specifier: ^1.17.16 + version: 1.17.17 + '@types/node': + specifier: ^22.0.0 + version: 22.19.17 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + +packages: + + '@jsep-plugin/assignment@1.3.0': + resolution: {integrity: sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==} + engines: {node: '>= 10.16.0'} + peerDependencies: + jsep: ^0.4.0||^1.0.0 + + '@jsep-plugin/regex@1.0.4': + resolution: {integrity: sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==} + engines: {node: '>= 10.16.0'} + peerDependencies: + jsep: ^0.4.0||^1.0.0 + + '@kubernetes/client-node@1.4.0': + resolution: {integrity: sha512-Zge3YvF7DJi264dU1b3wb/GmzR99JhUpqTvp+VGHfwZT+g7EOOYNScDJNZwXy9cszyIGPIs0VHr+kk8e95qqrA==} + + '@tsconfig/node22@22.0.5': + resolution: {integrity: sha512-hLf2ld+sYN/BtOJjHUWOk568dvjFQkHnLNa6zce25GIH+vxKfvTgm3qpaH6ToF5tu/NN0IH66s+Bb5wElHrLcw==} + + '@types/http-proxy@1.17.17': + resolution: {integrity: sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==} + + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + + '@types/node@22.19.17': + resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} + + '@types/node@24.12.2': + resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} + + '@types/stream-buffers@3.0.8': + resolution: {integrity: sha512-J+7VaHKNvlNPJPEJXX/fKa9DZtR/xPMwuIbe+yNOwp1YB+ApUOBv2aUpEoBJEi8nJgbgs1x8e73ttg0r1rSUdw==} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + b4a@1.8.0: + resolution: {integrity: sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + bare-events@2.8.2: + resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.6.0: + resolution: {integrity: sha512-2YkS7NuiJceSEbyEOdSNLE9tsGd+f4+f7C+Nik/MCk27SYdwIMPT/yRKvg++FZhQXgk0KWJKJyXX9RhVV0RGqA==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.8.7: + resolution: {integrity: sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.12.0: + resolution: {integrity: sha512-w28i8lkBgREV3rPXGbgK+BO66q+ZpKqRWrZLiCdmmUlLPrQ45CzkvRhN+7lnv00Gpi2zy5naRxnUFAxCECDm9g==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.4.0: + resolution: {integrity: sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hpagent@1.2.0: + resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} + engines: {node: '>=14'} + + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + isomorphic-ws@5.0.0: + resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} + peerDependencies: + ws: '*' + + jose@6.2.2: + resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsep@1.4.0: + resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} + engines: {node: '>= 10.16.0'} + + jsonpath-plus@10.4.0: + resolution: {integrity: sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==} + engines: {node: '>=18.0.0'} + hasBin: true + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + oauth4webapi@3.8.5: + resolution: {integrity: sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + openid-client@6.8.2: + resolution: {integrity: sha512-uOvTCndr4udZsKihJ68H9bUICrriHdUVJ6Az+4Ns6cW55rwM5h0bjVIzDz2SxgOI84LKjFyjOFvERLzdTUROGA==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + rfc4648@1.5.4: + resolution: {integrity: sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + stream-buffers@3.0.3: + resolution: {integrity: sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw==} + engines: {node: '>= 0.10.0'} + + streamx@2.25.0: + resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==} + + tar-fs@3.1.2: + resolution: {integrity: sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==} + + tar-stream@3.1.8: + resolution: {integrity: sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==} + + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + +snapshots: + + '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': + dependencies: + jsep: 1.4.0 + + '@jsep-plugin/regex@1.0.4(jsep@1.4.0)': + dependencies: + jsep: 1.4.0 + + '@kubernetes/client-node@1.4.0': + dependencies: + '@types/js-yaml': 4.0.9 + '@types/node': 24.12.2 + '@types/node-fetch': 2.6.13 + '@types/stream-buffers': 3.0.8 + form-data: 4.0.5 + hpagent: 1.2.0 + isomorphic-ws: 5.0.0(ws@8.20.0) + js-yaml: 4.1.1 + jsonpath-plus: 10.4.0 + node-fetch: 2.7.0 + openid-client: 6.8.2 + rfc4648: 1.5.4 + socks-proxy-agent: 8.0.5 + stream-buffers: 3.0.3 + tar-fs: 3.1.2 + ws: 8.20.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - encoding + - react-native-b4a + - supports-color + - utf-8-validate + + '@tsconfig/node22@22.0.5': {} + + '@types/http-proxy@1.17.17': + dependencies: + '@types/node': 22.19.17 + + '@types/js-yaml@4.0.9': {} + + '@types/node-fetch@2.6.13': + dependencies: + '@types/node': 22.19.17 + form-data: 4.0.5 + + '@types/node@22.19.17': + dependencies: + undici-types: 6.21.0 + + '@types/node@24.12.2': + dependencies: + undici-types: 7.16.0 + + '@types/stream-buffers@3.0.8': + dependencies: + '@types/node': 22.19.17 + + agent-base@7.1.4: {} + + argparse@2.0.1: {} + + asynckit@0.4.0: {} + + b4a@1.8.0: {} + + bare-events@2.8.2: {} + + bare-fs@4.6.0: + dependencies: + bare-events: 2.8.2 + bare-path: 3.0.0 + bare-stream: 2.12.0(bare-events@2.8.2) + bare-url: 2.4.0 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + bare-os@3.8.7: {} + + bare-path@3.0.0: + dependencies: + bare-os: 3.8.7 + + bare-stream@2.12.0(bare-events@2.8.2): + dependencies: + streamx: 2.25.0 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - react-native-b4a + + bare-url@2.4.0: + dependencies: + bare-path: 3.0.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + delayed-stream@1.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + eventemitter3@4.0.7: {} + + events-universal@1.0.1: + dependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - bare-abort-controller + + fast-fifo@1.3.2: {} + + follow-redirects@1.15.11: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hpagent@1.2.0: {} + + http-proxy@1.18.1: + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.11 + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + + ip-address@10.1.0: {} + + isomorphic-ws@5.0.0(ws@8.20.0): + dependencies: + ws: 8.20.0 + + jose@6.2.2: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsep@1.4.0: {} + + jsonpath-plus@10.4.0: + dependencies: + '@jsep-plugin/assignment': 1.3.0(jsep@1.4.0) + '@jsep-plugin/regex': 1.0.4(jsep@1.4.0) + jsep: 1.4.0 + + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + ms@2.1.3: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + oauth4webapi@3.8.5: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + openid-client@6.8.2: + dependencies: + jose: 6.2.2 + oauth4webapi: 3.8.5 + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + requires-port@1.0.0: {} + + rfc4648@1.5.4: {} + + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + + socks@2.8.7: + dependencies: + ip-address: 10.1.0 + smart-buffer: 4.2.0 + + stream-buffers@3.0.3: {} + + streamx@2.25.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + tar-fs@3.1.2: + dependencies: + pump: 3.0.4 + tar-stream: 3.1.8 + optionalDependencies: + bare-fs: 4.6.0 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar-stream@3.1.8: + dependencies: + b4a: 1.8.0 + bare-fs: 4.6.0 + fast-fifo: 1.3.2 + streamx: 2.25.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + teex@1.0.1: + dependencies: + streamx: 2.25.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + text-decoder@1.2.7: + dependencies: + b4a: 1.8.0 + transitivePeerDependencies: + - react-native-b4a + + tr46@0.0.3: {} + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + undici-types@7.16.0: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + wrappy@1.0.2: {} + + ws@8.20.0: {} diff --git a/packages/opencode-router/scripts/create-local-kubeconfig.sh b/packages/opencode-router/scripts/create-local-kubeconfig.sh new file mode 100755 index 000000000000..cfa163524172 --- /dev/null +++ b/packages/opencode-router/scripts/create-local-kubeconfig.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Create a temporary kubeconfig for local opencode-router development. +# This uses the opencode-router ServiceAccount token from the live cluster. +# +# Prerequisites: +# - kubectl configured and pointing at the homelab cluster +# - opencode-router namespace and ServiceAccount already deployed (pulumi up) +# +# Usage: +# ./scripts/create-local-kubeconfig.sh # 24h token (default) +# TOKEN_DURATION=168h ./scripts/create-local-kubeconfig.sh # 7-day token + +set -euo pipefail + +NAMESPACE="${OPENCODE_NAMESPACE:-opencode-router}" +SA_NAME="opencode-router" +KUBECONFIG_OUT="${KUBECONFIG_OUT:-/tmp/opencode-router-local.kubeconfig}" +TOKEN_DURATION="${TOKEN_DURATION:-24h}" + +echo "→ Creating ServiceAccount token for ${SA_NAME} in namespace ${NAMESPACE}..." +TOKEN=$(kubectl create token "${SA_NAME}" \ + --namespace "${NAMESPACE}" \ + --duration "${TOKEN_DURATION}") + +echo "→ Getting cluster server URL..." +SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}') +CA_DATA=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}') + +echo "→ Writing kubeconfig to ${KUBECONFIG_OUT}..." +cat > "${KUBECONFIG_OUT}" < # forward a specific pod + +set -euo pipefail + +NAMESPACE="${OPENCODE_NAMESPACE:-opencode-router}" +LOCAL_PORT="${LOCAL_PORT:-4096}" +POD_PORT="${POD_PORT:-4096}" +LABEL_SELECTOR="app.kubernetes.io/managed-by=opencode-router" + +POD_NAME="${1:-}" + +if [ -z "$POD_NAME" ]; then + # List all running session pods + PODS=$(kubectl get pods -n "${NAMESPACE}" -l "${LABEL_SELECTOR}" \ + --field-selector=status.phase=Running \ + --no-headers -o custom-columns=":metadata.name" 2>/dev/null || true) + + if [ -z "$PODS" ]; then + echo "✗ No running session pods found in namespace '${NAMESPACE}'" + echo " Create a session at http://localhost:3002 first." + exit 1 + fi + + COUNT=$(echo "$PODS" | wc -l | tr -d ' ') + if [ "$COUNT" -eq 1 ]; then + POD_NAME="$PODS" + else + echo "Multiple running pods — pick one:" + echo "$PODS" | nl -w2 -s'. ' + read -r -p "Enter number: " CHOICE + POD_NAME=$(echo "$PODS" | sed -n "${CHOICE}p") + fi +fi + +echo "→ Pod: $POD_NAME (namespace: $NAMESPACE)" +echo "→ Port-forwarding: localhost:${LOCAL_PORT} → pod:${POD_PORT}" +echo "" +echo "Router config: export DEV_POD_PROXY_TARGET=http://localhost:${LOCAL_PORT}" +echo "" + +kubectl port-forward "${POD_NAME}" "${LOCAL_PORT}:${POD_PORT}" -n "${NAMESPACE}" diff --git a/packages/opencode-router/src/api.ts b/packages/opencode-router/src/api.ts new file mode 100644 index 000000000000..90bc9d32b874 --- /dev/null +++ b/packages/opencode-router/src/api.ts @@ -0,0 +1,94 @@ +import type http from "node:http"; +import { config } from "./config.js"; +import { + type SessionKey, + ensurePVC, + ensurePod, + getPodState, + getSessionHash, + listUserSessions, +} from "./pod-manager.js"; + +function json(res: http.ServerResponse, status: number, body: unknown): void { + res.writeHead(status, { "Content-Type": "application/json" }).end(JSON.stringify(body)); +} + +async function readBody(req: http.IncomingMessage): Promise { + const chunks: Buffer[] = []; + for await (const chunk of req) chunks.push(chunk as Buffer); + return Buffer.concat(chunks).toString("utf-8"); +} + +/** Build the public URL for a session subdomain. */ +function sessionUrl(hash: string, req: http.IncomingMessage): string { + // Derive scheme from X-Forwarded-Proto (set by Traefik) or default to http in dev + const proto = (req.headers["x-forwarded-proto"] as string | undefined) ?? "http"; + return `${proto}://${hash}${config.routeSuffix}.${config.routerDomain}`; +} + +/** + * Handle API routes. Returns true if the route was handled. + * + * Routes: + * GET /api/sessions → list all sessions for the authenticated user + * POST /api/sessions → create a session {repoUrl, branch} → {hash, url, state} + * GET /api/sessions/:hash → get state of a specific session + */ +export async function handleApi( + req: http.IncomingMessage, + res: http.ServerResponse, + email: string +): Promise { + const url = req.url ?? "/"; + + // GET /api/sessions — list all sessions for this user, always includes email + if (url === "/api/sessions" && req.method === "GET") { + const sessions = await listUserSessions(email, req); + json(res, 200, { email, sessions }); + return true; + } + + // POST /api/sessions — create a new session + if (url === "/api/sessions" && req.method === "POST") { + const raw = await readBody(req); + let repoUrl: string; + let branch: string; + try { + const body = JSON.parse(raw); + repoUrl = typeof body.repoUrl === "string" ? body.repoUrl.trim() : ""; + branch = typeof body.branch === "string" ? body.branch.trim() : ""; + } catch { + json(res, 400, { error: "Invalid JSON" }); + return true; + } + + if (!repoUrl) { + json(res, 400, { error: "repoUrl is required" }); + return true; + } + if (!branch) { + json(res, 400, { error: "branch is required" }); + return true; + } + + const session: SessionKey = { email, repoUrl, branch }; + const hash = getSessionHash(email, repoUrl, branch); + + await ensurePVC(session); + await ensurePod(session); + + json(res, 201, { hash, url: sessionUrl(hash, req), state: "creating" }); + return true; + } + + // GET /api/sessions/:hash — get state of a specific session + const sessionMatch = url.match(/^\/api\/sessions\/([a-f0-9]{12})$/); + if (sessionMatch && req.method === "GET") { + const hash = sessionMatch[1]; + const state = await getPodState(hash); + json(res, 200, { hash, state, url: sessionUrl(hash, req) }); + return true; + } + + return false; +} diff --git a/packages/opencode-router/src/config.ts b/packages/opencode-router/src/config.ts new file mode 100644 index 000000000000..65529022fdda --- /dev/null +++ b/packages/opencode-router/src/config.ts @@ -0,0 +1,50 @@ +function required(name: string): string { + const value = process.env[name]; + if (!value) throw new Error(`Missing required environment variable: ${name}`); + return value; +} + +export const config = { + port: Number(process.env.PORT ?? 3000), + namespace: process.env.OPENCODE_NAMESPACE ?? "opencode", + opencodeImage: required("OPENCODE_IMAGE"), + opencodePort: 4096, + idleTimeoutMinutes: Number(process.env.IDLE_TIMEOUT_MINUTES ?? 30), + apiKeySecretName: process.env.API_KEY_SECRET_NAME ?? "opencode-api-keys", + configMapName: process.env.CONFIG_MAP_NAME ?? "opencode-config-dir", + imagePullSecretName: process.env.IMAGE_PULL_SECRET_NAME ?? "", + storageClass: process.env.STORAGE_CLASS ?? "", + storageSize: process.env.STORAGE_SIZE ?? "2Gi", + defaultGitRepo: process.env.DEFAULT_GIT_REPO, + publicDir: process.env.PUBLIC_DIR ?? new URL("../public", import.meta.url).pathname, + /** + * The base domain (e.g. "no-panic.org"). + * Sessions are served at https://.. + * Required — subdomain routing is the only supported session isolation strategy. + */ + routerDomain: required("ROUTER_DOMAIN"), + /** + * Suffix appended to the session hash to form the session subdomain. + * e.g. ROUTE_SUFFIX="-oc" → session URL: https://-oc. + * Defaults to "" (hash.) for local dev / backward compatibility. + * Set to "-oc" in production so sessions stay at the first subdomain level, + * covered by the *.no-panic.org Universal SSL certificate. + */ + routeSuffix: process.env.ROUTE_SUFFIX ?? "", + /** + * Dev-only: when set, the router proxies the setup UI to this Vite dev server URL + * instead of serving static files from publicDir. Enables HMR without a redirect loop. + * Example: DEV_VITE_URL=http://localhost:5173 + */ + devViteUrl: process.env.DEV_VITE_URL, + /** + * Dev-only: assumed email when X-Auth-Request-Email header is absent (never set in production) + */ + devEmail: process.env.DEV_EMAIL, + /** + * Dev-only: fixed proxy target (e.g. "http://localhost:4096") used instead of looking up + * the pod IP. Needed when running the router outside the cluster where pod IPs are unreachable. + * Set this and port-forward the user pod: kubectl port-forward 4096:4096 -n opencode-router + */ + devPodProxyTarget: process.env.DEV_POD_PROXY_TARGET, +} as const; diff --git a/packages/opencode-router/src/index.ts b/packages/opencode-router/src/index.ts new file mode 100644 index 000000000000..709a2980ed64 --- /dev/null +++ b/packages/opencode-router/src/index.ts @@ -0,0 +1,153 @@ +import http from "node:http"; +import httpProxy from "http-proxy"; +import { handleApi } from "./api.js"; +import { config } from "./config.js"; +import { deleteIdlePods, getPodIP, updateLastActivity } from "./pod-manager.js"; +import { serveStatic } from "./static.js"; + +/** + * Extract session hash from the Host header. + * Returns the 12-char hex hash if the request is on a session subdomain, + * or null if on the root domain (the router SPA). + * + * Session hostname format: . + * e.g. with ROUTE_SUFFIX="-oc" and ROUTER_DOMAIN="no-panic.org": + * abc123def456-oc.no-panic.org → "abc123def456" + * + * With ROUTE_SUFFIX="" (local dev, ROUTER_DOMAIN="localhost:3002"): + * abc123def456.localhost:3002 → "abc123def456" + */ +function getSessionHash(host: string): string | null { + // Strip port from both the incoming Host header and routerDomain before comparing, + // so that "abc123-oc.localhost:3002" correctly matches routerDomain "localhost:3002". + const hostname = host.split(":")[0]; + const routerHostname = config.routerDomain.split(":")[0]; + // Full suffix to strip: e.g. "-oc.no-panic.org" or ".localhost" + const suffix = `${config.routeSuffix}.${routerHostname}`; + if (!hostname.endsWith(suffix)) return null; + const sub = hostname.slice(0, hostname.length - suffix.length); + // Must be exactly a 12-char hex session hash + if (/^[a-f0-9]{12}$/.test(sub)) return sub; + return null; +} + +function getEmail(req: http.IncomingMessage): string | null { + const header = req.headers["x-auth-request-email"]; + if (typeof header === "string" && header.length > 0) return header; + // Dev fallback: use DEV_EMAIL env var when running locally without oauth2-proxy + if (config.devEmail) return config.devEmail; + return null; +} + +const proxy = httpProxy.createProxyServer({}); + +proxy.on("error", (err, _req, res) => { + console.error("Proxy error:", err.message); + if (res instanceof http.ServerResponse && !res.headersSent) { + res.writeHead(502).end("Bad Gateway"); + } +}); + +const server = http.createServer(async (req, res) => { + const email = getEmail(req); + if (!email) { + res.writeHead(401, { "Content-Type": "text/plain" }).end("Missing user identity"); + return; + } + + try { + const host = req.headers.host ?? ""; + const sessionHash = getSessionHash(host); + + if (sessionHash) { + // ── Session subdomain: .opencode-router.domain ────────────────── + // All traffic (HTTP + WS) is proxied directly to the session's pod. + // No path stripping — opencode is rooted at /. + const ip = await getPodIP(sessionHash); + if (!ip) { + res + .writeHead(503, { "Content-Type": "application/json" }) + .end(JSON.stringify({ error: "session not ready", hash: sessionHash })); + return; + } + updateLastActivity(sessionHash); + const target = config.devPodProxyTarget ?? `http://${ip}:${config.opencodePort}`; + proxy.web(req, res, { target }); + return; + } + + // ── Root domain: opencode-router.domain ─────────────────────────────── + // Router's own API + const url = req.url ?? "/"; + if (url.startsWith("/api/")) { + const handled = await handleApi(req, res, email); + if (handled) return; + } + + // Setup SPA (or Vite dev server) + if (config.devViteUrl) { + proxy.web(req, res, { target: config.devViteUrl }); + } else { + serveStatic(config.publicDir, req, res); + } + } catch (err) { + console.error(`Error handling request for ${req.headers.host}:`, err); + if (!res.headersSent) { + res.writeHead(500, { "Content-Type": "text/plain" }).end("Internal server error"); + } + } +}); + +server.on("upgrade", async (req, socket, head) => { + const email = getEmail(req); + if (!email) { + socket.destroy(); + return; + } + + try { + const host = req.headers.host ?? ""; + const sessionHash = getSessionHash(host); + + if (sessionHash) { + // WebSocket on a session subdomain — proxy to the pod + const ip = await getPodIP(sessionHash); + if (!ip) { + socket.destroy(); + return; + } + updateLastActivity(sessionHash); + const target = config.devPodProxyTarget ?? `http://${ip}:${config.opencodePort}`; + proxy.ws(req, socket, head, { target }); + return; + } + + // WebSocket on root domain — Vite HMR in dev mode + if (config.devViteUrl) { + proxy.ws(req, socket, head, { target: config.devViteUrl }); + return; + } + + socket.destroy(); + } catch (err) { + console.error(`WebSocket upgrade error for ${req.headers.host}:`, err); + socket.destroy(); + } +}); + +// Background: clean up idle pods every 60 seconds +const cleanupInterval = setInterval(deleteIdlePods, 60_000); + +function shutdown() { + console.log("Shutting down..."); + clearInterval(cleanupInterval); + server.close(() => process.exit(0)); + setTimeout(() => process.exit(1), 10_000); +} + +process.on("SIGTERM", shutdown); +process.on("SIGINT", shutdown); + +server.listen(config.port, () => { + console.log(`opencode-router listening on :${config.port} | domain: ${config.routerDomain}`); +}); diff --git a/packages/opencode-router/src/pod-manager.ts b/packages/opencode-router/src/pod-manager.ts new file mode 100644 index 000000000000..69602c7f4305 --- /dev/null +++ b/packages/opencode-router/src/pod-manager.ts @@ -0,0 +1,403 @@ +import crypto from "node:crypto"; +import fs from "node:fs"; +import * as k8s from "@kubernetes/client-node"; +import { config } from "./config.js"; + +const kc = new k8s.KubeConfig(); +// loadFromCluster() does not throw when not in a pod — it silently produces +// an invalid config with server=undefined. Check for the SA token file first. +if (fs.existsSync("/var/run/secrets/kubernetes.io/serviceaccount/token")) { + kc.loadFromCluster(); +} else { + kc.loadFromDefault(); +} +const k8sApi = kc.makeApiClient(k8s.CoreV1Api); + +const LABEL_SESSION_HASH = "opencode.ai/session-hash"; +const LABEL_MANAGED_BY = "app.kubernetes.io/managed-by"; +const MANAGED_BY_VALUE = "opencode-router"; +const ANNOTATION_LAST_ACTIVITY = "opencode.ai/last-activity"; +const ANNOTATION_USER_EMAIL = "opencode.ai/user-email"; +const ANNOTATION_REPO_URL = "opencode.ai/repo-url"; +const ANNOTATION_BRANCH = "opencode.ai/branch"; + +/** In-memory throttle for annotation updates: hash → last update epoch ms */ +const activityThrottle = new Map(); +const THROTTLE_MS = 60_000; + +export interface SessionKey { + email: string; + repoUrl: string; + branch: string; +} + +export interface SessionInfo { + hash: string; + email: string; + repoUrl: string; + branch: string; + state: PodState; + url: string; +} + +/** + * Compute a deterministic, DNS-safe 12-char hex hash for a (email, repoUrl, branch) triple. + * This is the stable identity for a session — used for pod name, PVC name, and URL slug. + */ +export function getSessionHash(email: string, repoUrl: string, branch: string): string { + const key = `${email.toLowerCase().trim()}:${repoUrl.trim()}:${branch.trim()}`; + return crypto.createHash("sha256").update(key).digest("hex").slice(0, 12); +} + +function podName(hash: string): string { + return `opencode-session-${hash}`; +} + +function pvcName(hash: string): string { + return `opencode-pvc-${hash}`; +} + +function sessionLabels(hash: string): Record { + return { + [LABEL_SESSION_HASH]: hash, + [LABEL_MANAGED_BY]: MANAGED_BY_VALUE, + }; +} + +/** + * Create PVC for a session if it doesn't exist. Idempotent. + */ +export async function ensurePVC(session: SessionKey): Promise { + const hash = getSessionHash(session.email, session.repoUrl, session.branch); + const name = pvcName(hash); + + try { + await k8sApi.readNamespacedPersistentVolumeClaim({ name, namespace: config.namespace }); + return; // already exists + } catch (err) { + if (!isNotFound(err)) throw err; + } + + const pvc: k8s.V1PersistentVolumeClaim = { + metadata: { + name, + namespace: config.namespace, + labels: sessionLabels(hash), + annotations: { + [ANNOTATION_USER_EMAIL]: session.email, + [ANNOTATION_REPO_URL]: session.repoUrl, + [ANNOTATION_BRANCH]: session.branch, + }, + }, + spec: { + accessModes: ["ReadWriteOnce"], + ...(config.storageClass && { storageClassName: config.storageClass }), + resources: { + requests: { storage: config.storageSize }, + }, + }, + }; + + try { + await k8sApi.createNamespacedPersistentVolumeClaim({ namespace: config.namespace, body: pvc }); + } catch (err) { + if (!isConflict(err)) throw err; + } +} + +export type PodState = "none" | "creating" | "running"; + +/** + * Return the current state of a session's pod by its hash. + */ +export async function getPodState(hash: string): Promise { + const name = podName(hash); + try { + const pod = await k8sApi.readNamespacedPod({ name, namespace: config.namespace }); + if (pod.status?.phase === "Running" && pod.status.podIP) return "running"; + return "creating"; + } catch (err) { + if (isNotFound(err)) return "none"; + throw err; + } +} + +/** + * Create pod for a session if it doesn't exist. Idempotent. Must call ensurePVC first. + * + * The git-init container: + * - Clones repoUrl into /workspace if not already cloned + * - Checks out `branch` if it exists remotely, otherwise creates it from the default branch + */ +export async function ensurePod(session: SessionKey): Promise { + const hash = getSessionHash(session.email, session.repoUrl, session.branch); + const name = podName(hash); + + try { + await k8sApi.readNamespacedPod({ name, namespace: config.namespace }); + return hash; // already exists + } catch (err) { + if (!isNotFound(err)) throw err; + } + + const now = new Date().toISOString(); + const { repoUrl, branch, email } = session; + + // The repo is cloned directly into /workspace (init container) which maps to /projects/ + // via subPath "projects". The main container mounts the full PVC at /home/opencode (no subPath), + // so the cloned repo is at /home/opencode/projects/ inside the main container. We set workingDir + // accordingly so opencode serve starts in the git repo and correctly discovers the project. + const workspacePath = `/home/opencode/projects`; + + // Use GIT_SAFE="git -c safe.directory=/workspace" to avoid needing a writable $HOME + // for "git config --global". The -c flag applies the config inline for each invocation. + const gitInitScript = [ + `set -e`, + `GIT="git -c safe.directory=/workspace"`, + `if [ ! -d /workspace/.git ]; then`, + ` git clone "${repoUrl}" /workspace`, + `fi`, + `cd /workspace`, + `$GIT fetch --all`, + // Check out existing remote branch, or create a new one from the current HEAD + `if $GIT ls-remote --exit-code --heads origin "${branch}" > /dev/null 2>&1; then`, + ` $GIT checkout -B "${branch}" "origin/${branch}"`, + `else`, + ` $GIT checkout -b "${branch}"`, + `fi`, + ].join("\n"); + + const initContainers: k8s.V1Container[] = [ + { + name: "git-init", + securityContext: { + runAsUser: 1000, + runAsGroup: 1000, + allowPrivilegeEscalation: false, + runAsNonRoot: true, + capabilities: { drop: ["ALL"] }, + seccompProfile: { type: "RuntimeDefault" }, + }, + image: "alpine/git:latest", + command: ["sh", "-c"], + args: [gitInitScript], + env: [ + // Provide a writable HOME so git doesn't try to write to /.gitconfig + { name: "HOME", value: "/tmp" }, + ], + volumeMounts: [{ name: "user-data", mountPath: "/workspace", subPath: "projects" }], + }, + ]; + + const pod: k8s.V1Pod = { + metadata: { + name, + namespace: config.namespace, + labels: sessionLabels(hash), + annotations: { + [ANNOTATION_LAST_ACTIVITY]: now, + [ANNOTATION_USER_EMAIL]: email, + [ANNOTATION_REPO_URL]: repoUrl, + [ANNOTATION_BRANCH]: branch, + }, + }, + spec: { + restartPolicy: "Always", + securityContext: { + runAsUser: 1000, + runAsGroup: 1000, + fsGroup: 1000, + runAsNonRoot: true, + seccompProfile: { type: "RuntimeDefault" }, + }, + ...(config.imagePullSecretName + ? { imagePullSecrets: [{ name: config.imagePullSecretName }] } + : {}), + initContainers, + containers: [ + { + name: "opencode", + image: config.opencodeImage, + // Start the process in the cloned repo directory so opencode discovers the git project. + // The init container clones directly into /workspace (subPath "projects" on the PVC). + // The main container mounts the full PVC at /home/opencode, so the repo is at + // /home/opencode/projects/. + workingDir: workspacePath, + command: [ + "opencode", + "serve", + "--hostname", + "0.0.0.0", + "--port", + String(config.opencodePort), + ], + ports: [{ containerPort: config.opencodePort }], + envFrom: [{ secretRef: { name: config.apiKeySecretName } }], + securityContext: { + allowPrivilegeEscalation: false, + runAsNonRoot: true, + capabilities: { drop: ["ALL"] }, + seccompProfile: { type: "RuntimeDefault" }, + }, + volumeMounts: [ + { name: "user-data", mountPath: "/home/opencode" }, + { name: "opencode-config", mountPath: "/home/opencode/.opencode", readOnly: true }, + ], + }, + ], + volumes: [ + { + name: "user-data", + persistentVolumeClaim: { claimName: pvcName(hash) }, + }, + { + name: "opencode-config", + configMap: { name: config.configMapName }, + }, + ], + }, + }; + + try { + await k8sApi.createNamespacedPod({ namespace: config.namespace, body: pod }); + } catch (err) { + if (!isConflict(err)) throw err; + } + + return hash; +} + +/** + * Return the pod's IP if it is Running, or null otherwise. Looks up by session hash. + */ +export async function getPodIP(hash: string): Promise { + const name = podName(hash); + try { + const pod = await k8sApi.readNamespacedPod({ name, namespace: config.namespace }); + if (pod.status?.phase === "Running" && pod.status.podIP) return pod.status.podIP; + return null; + } catch (err) { + if (isNotFound(err)) return null; + throw err; + } +} + +/** + * List all sessions belonging to a user (by email annotation). + * Pass the incoming request so URLs can be built with the correct scheme. + */ +export async function listUserSessions( + email: string, + req: import("node:http").IncomingMessage +): Promise { + const response = await k8sApi.listNamespacedPod({ + namespace: config.namespace, + labelSelector: `${LABEL_MANAGED_BY}=${MANAGED_BY_VALUE}`, + }); + + const proto = (req.headers["x-forwarded-proto"] as string | undefined) ?? "http"; + + const sessions: SessionInfo[] = []; + for (const pod of response.items) { + const ann = pod.metadata?.annotations ?? {}; + if (ann[ANNOTATION_USER_EMAIL] !== email) continue; + + const hash = pod.metadata?.labels?.[LABEL_SESSION_HASH]; + if (!hash) continue; + + const repoUrl = ann[ANNOTATION_REPO_URL] ?? ""; + const branch = ann[ANNOTATION_BRANCH] ?? ""; + const state: PodState = + pod.status?.phase === "Running" && pod.status.podIP ? "running" : "creating"; + + sessions.push({ + hash, + email, + repoUrl, + branch, + state, + url: `${proto}://${hash}${config.routeSuffix}.${config.routerDomain}`, + }); + } + + return sessions; +} + +/** + * Update the last-activity annotation on a session's pod. + * Throttled to at most once per minute per session to reduce K8s API load. + */ +export function updateLastActivity(hash: string): void { + const now = Date.now(); + const last = activityThrottle.get(hash) ?? 0; + if (now - last < THROTTLE_MS) return; + + activityThrottle.set(hash, now); + const name = podName(hash); + + k8sApi + .patchNamespacedPod({ + name, + namespace: config.namespace, + body: { + metadata: { + annotations: { [ANNOTATION_LAST_ACTIVITY]: new Date(now).toISOString() }, + }, + }, + }) + .catch((err) => { + console.error(`Failed to update last-activity for ${name}:`, err); + }); +} + +/** + * Delete pods that have been idle longer than the configured timeout. + * Only deletes pods — PVCs are preserved. + */ +export async function deleteIdlePods(): Promise { + const cutoff = Date.now() - config.idleTimeoutMinutes * 60_000; + + try { + const response = await k8sApi.listNamespacedPod({ + namespace: config.namespace, + labelSelector: `${LABEL_MANAGED_BY}=${MANAGED_BY_VALUE}`, + }); + + for (const pod of response.items) { + const lastActivity = pod.metadata?.annotations?.[ANNOTATION_LAST_ACTIVITY]; + if (!lastActivity) continue; + + const lastMs = new Date(lastActivity).getTime(); + if (lastMs < cutoff) { + const name = pod.metadata?.name; + if (!name) continue; + console.log(`Deleting idle pod ${name} (last activity: ${lastActivity})`); + await k8sApi + .deleteNamespacedPod({ name, namespace: config.namespace }) + .catch((err) => console.error(`Failed to delete pod ${name}:`, err)); + + const hash = pod.metadata?.labels?.[LABEL_SESSION_HASH]; + if (hash) activityThrottle.delete(hash); + } + } + } catch (err) { + console.error("Failed to list pods for idle cleanup:", err); + } +} + +function hasCode(err: unknown): err is { code: number } { + return ( + typeof err === "object" && + err !== null && + "code" in err && + typeof (err as Record).code === "number" + ); +} + +function isNotFound(err: unknown): boolean { + return hasCode(err) && err.code === 404; +} + +function isConflict(err: unknown): boolean { + return hasCode(err) && err.code === 409; +} diff --git a/packages/opencode-router/src/static.ts b/packages/opencode-router/src/static.ts new file mode 100644 index 000000000000..4bde1bbaa242 --- /dev/null +++ b/packages/opencode-router/src/static.ts @@ -0,0 +1,48 @@ +import http from "node:http" +import fs from "node:fs" +import path from "node:path" + +const MIME_TYPES: Record = { + ".html": "text/html; charset=utf-8", + ".js": "application/javascript; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".json": "application/json; charset=utf-8", + ".png": "image/png", + ".svg": "image/svg+xml", + ".ico": "image/x-icon", + ".woff": "font/woff", + ".woff2": "font/woff2", +} + +/** + * Serve static files from the given directory with SPA fallback to index.html. + */ +export function serveStatic( + publicDir: string, + req: http.IncomingMessage, + res: http.ServerResponse, +): void { + const url = new URL(req.url ?? "/", "http://localhost") + let filePath = path.join(publicDir, url.pathname) + + // Prevent directory traversal + if (!filePath.startsWith(publicDir)) { + res.writeHead(403).end("Forbidden") + return + } + + // Try the exact path, then fall back to index.html for SPA routing + if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) { + filePath = path.join(publicDir, "index.html") + } + + if (!fs.existsSync(filePath)) { + res.writeHead(404).end("Not Found") + return + } + + const ext = path.extname(filePath) + const contentType = MIME_TYPES[ext] ?? "application/octet-stream" + const content = fs.readFileSync(filePath) + res.writeHead(200, { "Content-Type": contentType }).end(content) +} diff --git a/packages/opencode-router/tsconfig.json b/packages/opencode-router/tsconfig.json new file mode 100644 index 000000000000..a1067da475b3 --- /dev/null +++ b/packages/opencode-router/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "module": "nodenext", + "moduleResolution": "nodenext", + "declaration": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src"] +} From 75d07d6d197d222cee60ebcb3d9609b845edc0fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Sun, 12 Apr 2026 23:27:18 +0200 Subject: [PATCH 002/616] feat(deployment): add homelab k8s deployment stack for opencode-router (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Intent Deploy opencode-router and its cloudflare-operator sidecar to the homelab Kubernetes cluster from this repo, with full CI/CD and a local make-based workflow. The homelab repo previously owned these deployment recipes; they now live here so the opencode team can ship infrastructure changes independently. # Key Components Affected - deployment/homelab/: standalone Pulumi stack (outside bun workspace to avoid catalog: protocol conflicts) that deploys the router + operator sidecar using @mrsimpson/homelab-core-components (npmjs.com) and a StackReference to the homelab base stack for shared infra facts - deployment/opencode-cloudflare-operator/: operator source + Dockerfile moved here from homelab repo; now has its own package-lock.json so the Docker build can use npm ci - deployment/homelab/Makefile: local CD — build-router, build-operator, build-push, deploy, release targets covering the full build → push → deploy cycle; image tags encode package version + git SHA matching CI convention - .github/workflows/build-cloudflare-operator.yml: builds and pushes the operator image to GHCR on changes to deployment/opencode-cloudflare-operator/ - .github/workflows/build-opencode-router.yml: builds and pushes the router image on changes to packages/opencode-router/ - .github/workflows/deploy-homelab.yml: runs pulumi up in deployment/homelab/ after operator image builds succeed; no auth needed for npm install since @mrsimpson/homelab-core-components is now on public npmjs.com - packages/opencode-router/scripts/build-image.sh: local build script for the router image (kept in packages/ since the router stays there) # Side effects - deployment/ is intentionally not a bun workspace package; it is a plain npm directory to avoid bun catalog: protocol incompatibilities with Node.js tooling - PULUMI_ACCESS_TOKEN, KUBECONFIG (homelab cluster), and GITHUB_TOKEN must be set as GitHub Actions secrets in this repo for CI to work --- .../workflows/build-cloudflare-operator.yml | 62 + .github/workflows/build-opencode-router.yml | 58 + .github/workflows/deploy-homelab.yml | 65 + deployment/homelab/.gitignore | 11 + deployment/homelab/Makefile | 185 + deployment/homelab/Pulumi.dev.yaml | 9 + deployment/homelab/Pulumi.yaml | 24 + deployment/homelab/README.md | 104 + deployment/homelab/package-lock.json | 5224 +++++++++++++++++ deployment/homelab/package.json | 20 + deployment/homelab/src/index.ts | 395 ++ deployment/homelab/tsconfig.json | 15 + .../opencode-cloudflare-operator/Dockerfile | 16 + .../opencode-cloudflare-operator/README.md | 81 + .../package-lock.json | 848 +++ .../opencode-cloudflare-operator/package.json | 21 + .../src/cloudflare.ts | 189 + .../src/config.ts | 52 + .../opencode-cloudflare-operator/src/index.ts | 165 + .../src/ingressroute.ts | 198 + .../tsconfig.json | 10 + .../opencode-router/scripts/build-image.sh | 136 + 22 files changed, 7888 insertions(+) create mode 100644 .github/workflows/build-cloudflare-operator.yml create mode 100644 .github/workflows/build-opencode-router.yml create mode 100644 .github/workflows/deploy-homelab.yml create mode 100644 deployment/homelab/.gitignore create mode 100644 deployment/homelab/Makefile create mode 100644 deployment/homelab/Pulumi.dev.yaml create mode 100644 deployment/homelab/Pulumi.yaml create mode 100644 deployment/homelab/README.md create mode 100644 deployment/homelab/package-lock.json create mode 100644 deployment/homelab/package.json create mode 100644 deployment/homelab/src/index.ts create mode 100644 deployment/homelab/tsconfig.json create mode 100644 deployment/opencode-cloudflare-operator/Dockerfile create mode 100644 deployment/opencode-cloudflare-operator/README.md create mode 100644 deployment/opencode-cloudflare-operator/package-lock.json create mode 100644 deployment/opencode-cloudflare-operator/package.json create mode 100644 deployment/opencode-cloudflare-operator/src/cloudflare.ts create mode 100644 deployment/opencode-cloudflare-operator/src/config.ts create mode 100644 deployment/opencode-cloudflare-operator/src/index.ts create mode 100644 deployment/opencode-cloudflare-operator/src/ingressroute.ts create mode 100644 deployment/opencode-cloudflare-operator/tsconfig.json create mode 100755 packages/opencode-router/scripts/build-image.sh diff --git a/.github/workflows/build-cloudflare-operator.yml b/.github/workflows/build-cloudflare-operator.yml new file mode 100644 index 000000000000..704031c72141 --- /dev/null +++ b/.github/workflows/build-cloudflare-operator.yml @@ -0,0 +1,62 @@ +name: Build opencode-cloudflare-operator image + +on: + push: + branches: + - dev + - main + - homelab-deployment + paths: + - deployment/opencode-cloudflare-operator/** + - .github/workflows/build-cloudflare-operator.yml + workflow_dispatch: + inputs: + push: + description: "Push image to GHCR" + type: boolean + default: true + +permissions: + contents: read + packages: write + +jobs: + build: + runs-on: ubuntu-latest + env: + IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/opencode-cloudflare-operator + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Get version from package.json + id: version + run: | + VERSION=$(node -p "require('./deployment/opencode-cloudflare-operator/package.json').version") + SHA_SHORT=${GITHUB_SHA::7} + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "tag=${VERSION}-${GITHUB_REF_NAME}.${SHA_SHORT}" >> $GITHUB_OUTPUT + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: deployment/opencode-cloudflare-operator + platforms: linux/amd64 + push: true + tags: | + ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} + ${{ env.IMAGE_NAME }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Output image tag + run: echo "Built and pushed ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}" diff --git a/.github/workflows/build-opencode-router.yml b/.github/workflows/build-opencode-router.yml new file mode 100644 index 000000000000..84aca9ca8d75 --- /dev/null +++ b/.github/workflows/build-opencode-router.yml @@ -0,0 +1,58 @@ +name: Build opencode-router image + +on: + push: + branches: + - dev + - main + - homelab-deployment + paths: + - packages/opencode-router/** + - .github/workflows/build-opencode-router.yml + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + build: + runs-on: ubuntu-latest + env: + IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/opencode-router + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Get version from package.json + id: version + run: | + VERSION=$(node -p "require('./packages/opencode-router/package.json').version") + SHA_SHORT=${GITHUB_SHA::7} + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "tag=${VERSION}-${GITHUB_REF_NAME}.${SHA_SHORT}" >> $GITHUB_OUTPUT + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: packages/opencode-router/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} + ${{ env.IMAGE_NAME }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Output image tag + run: echo "Built and pushed ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}" diff --git a/.github/workflows/deploy-homelab.yml b/.github/workflows/deploy-homelab.yml new file mode 100644 index 000000000000..5fd032927162 --- /dev/null +++ b/.github/workflows/deploy-homelab.yml @@ -0,0 +1,65 @@ +name: Deploy to homelab + +on: + # Run after images are built + workflow_run: + workflows: + - "Build opencode-cloudflare-operator image" + types: + - completed + branches: + - dev + - main + + # Also allow manual deploys with explicit image overrides + workflow_dispatch: + inputs: + routerImage: + description: "opencode-router image (leave blank to use current stack config)" + type: string + required: false + cfOperatorImage: + description: "cloudflare-operator image (leave blank to use current stack config)" + type: string + required: false + +permissions: + contents: read + id-token: write # for OIDC-based kubeconfig (if used) + +jobs: + deploy: + runs-on: ubuntu-latest + # Only deploy if the triggering workflow succeeded (or if manually triggered) + if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Install Pulumi CLI + uses: pulumi/actions@v6 + + - name: Install homelab stack dependencies + run: npm install + working-directory: deployment/homelab + + - name: Update image tags (if provided) + if: ${{ inputs.routerImage != '' || inputs.cfOperatorImage != '' }} + run: | + cd deployment/homelab + [ -n "${{ inputs.routerImage }}" ] && pulumi config set code:routerImage "${{ inputs.routerImage }}" + [ -n "${{ inputs.cfOperatorImage }}" ] && pulumi config set code:cfOperatorImage "${{ inputs.cfOperatorImage }}" + env: + PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} + + - name: Pulumi up + run: pulumi up --yes --non-interactive + working-directory: deployment/homelab + env: + PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} + # HOMELAB_KUBECONFIG: base64-encoded kubeconfig for the homelab cluster. + # Set as a repo secret. The Pulumi k8s provider reads KUBECONFIG from env. + KUBECONFIG: ${{ secrets.HOMELAB_KUBECONFIG }} diff --git a/deployment/homelab/.gitignore b/deployment/homelab/.gitignore new file mode 100644 index 000000000000..dd71e552db35 --- /dev/null +++ b/deployment/homelab/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +dist/ + +# Local .npmrc contains auth token — never commit +.npmrc + +# Pulumi stack state is stored in Pulumi Cloud, not locally +*.pulumi/ + +# OS +.DS_Store diff --git a/deployment/homelab/Makefile b/deployment/homelab/Makefile new file mode 100644 index 000000000000..d267b2a8eef8 --- /dev/null +++ b/deployment/homelab/Makefile @@ -0,0 +1,185 @@ +# ============================================================================== +# Makefile — local CD for opencode homelab deployment +# +# This Makefile covers the full local workflow: +# build images → push to GHCR → update Pulumi config → deploy to cluster +# +# Prerequisites: +# - Docker (with buildx) installed and running +# - Node.js installed (for reading package versions) +# - Pulumi CLI installed (https://www.pulumi.com/docs/install/) +# - PULUMI_ACCESS_TOKEN exported in your shell +# - KUBECONFIG pointing to the homelab cluster +# - GITHUB_PAT exported (GitHub PAT with write:packages scope for push) +# +# Quickstart: +# 1. make build-push — build all images and push to GHCR +# 2. make deploy — update Pulumi config with new tags and deploy +# +# Or all in one: +# make release +# ============================================================================== + +# === CUSTOMIZE THESE =========================================================== +STACK_NAME ?= dev +GHCR_USER ?= mrsimpson +HOMELAB_STACK ?= mrsimpson/homelab/dev +# GITHUB_PAT must be set in your shell (write:packages scope) +# export GITHUB_PAT=ghp_... +# =============================================================================== + +# Directory layout (all relative to this Makefile): +# deployment/homelab/ ← this file lives here (PULUMI_DIR) +# deployment/opencode-cloudflare-operator/ ← operator source + Dockerfile +# packages/opencode-router/ ← router source + Dockerfile (monorepo root) +PULUMI_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +DEPLOY_DIR := $(PULUMI_DIR)/.. +MONOREPO_ROOT := $(DEPLOY_DIR)/.. + +# Derived image names +ROUTER_IMAGE := ghcr.io/$(GHCR_USER)/opencode-router +OPERATOR_IMAGE := ghcr.io/$(GHCR_USER)/opencode-cloudflare-operator + +# Versions read from package.json files +ROUTER_VERSION := $(shell node -p "require('$(MONOREPO_ROOT)/packages/opencode-router/package.json').version" 2>/dev/null) +OPERATOR_VERSION := $(shell node -p "require('$(DEPLOY_DIR)/opencode-cloudflare-operator/package.json').version" 2>/dev/null) + +# Git short SHA for uniqueness +SHA_SHORT := $(shell git -C $(MONOREPO_ROOT) rev-parse --short HEAD 2>/dev/null || echo "local") + +# Final image tags (matches CI pattern: -.) +ROUTER_TAG := $(ROUTER_VERSION)-local.$(SHA_SHORT) +OPERATOR_TAG := $(OPERATOR_VERSION)-local.$(SHA_SHORT) + +ROUTER_FULL := $(ROUTER_IMAGE):$(ROUTER_TAG) +OPERATOR_FULL := $(OPERATOR_IMAGE):$(OPERATOR_TAG) + +.PHONY: help build-router build-operator build push-router push-operator push build-push deploy release preview destroy logs + +# Default target +help: + @echo "" + @echo "opencode homelab — local CD" + @echo "" + @echo "Image build targets:" + @echo " build-router Build opencode-router image (linux/amd64)" + @echo " build-operator Build opencode-cloudflare-operator image (linux/amd64)" + @echo " build Build both images" + @echo " push-router Push router image to GHCR (requires GITHUB_PAT)" + @echo " push-operator Push operator image to GHCR (requires GITHUB_PAT)" + @echo " push Push both images" + @echo " build-push Build + push both images" + @echo "" + @echo "Deployment targets:" + @echo " preview Pulumi preview (dry-run)" + @echo " deploy Update Pulumi config with current image tags + pulumi up" + @echo " release build-push + deploy" + @echo " destroy Destroy all cluster resources (asks for confirmation)" + @echo " logs Show recent Pulumi stack history" + @echo "" + @echo "Active configuration:" + @echo " Stack : $(STACK_NAME)" + @echo " GHCR user : $(GHCR_USER)" + @echo " Router : $(ROUTER_FULL)" + @echo " Operator : $(OPERATOR_FULL)" + @echo "" + +# ------------------------------------------------------------------------------ +# Image builds — always target linux/amd64 (homelab cluster is x86_64) +# ------------------------------------------------------------------------------ + +# Router: build context is monorepo root (Dockerfile references other packages/) +build-router: + @echo "→ Building $(ROUTER_FULL) ..." + docker buildx build \ + --platform linux/amd64 \ + --file $(MONOREPO_ROOT)/packages/opencode-router/Dockerfile \ + --tag $(ROUTER_FULL) \ + --tag $(ROUTER_IMAGE):latest \ + --load \ + $(MONOREPO_ROOT) + @echo "✓ Built $(ROUTER_FULL)" + +# Operator: self-contained, build context is deployment/opencode-cloudflare-operator/ +build-operator: + @echo "→ Building $(OPERATOR_FULL) ..." + docker buildx build \ + --platform linux/amd64 \ + --file $(DEPLOY_DIR)/opencode-cloudflare-operator/Dockerfile \ + --tag $(OPERATOR_FULL) \ + --tag $(OPERATOR_IMAGE):latest \ + --load \ + $(DEPLOY_DIR)/opencode-cloudflare-operator + @echo "✓ Built $(OPERATOR_FULL)" + +build: build-router build-operator + +# ------------------------------------------------------------------------------ +# Push — requires GITHUB_PAT with write:packages scope +# ------------------------------------------------------------------------------ + +_ghcr-login: + @if [ -z "$(GITHUB_PAT)" ]; then \ + echo "✗ GITHUB_PAT is not set. Export a GitHub PAT with write:packages scope:"; \ + echo " export GITHUB_PAT=ghp_..."; \ + exit 1; \ + fi + @echo "$(GITHUB_PAT)" | docker login ghcr.io -u $(GHCR_USER) --password-stdin + +push-router: _ghcr-login + @echo "→ Pushing $(ROUTER_FULL) ..." + docker buildx build \ + --platform linux/amd64 \ + --file $(MONOREPO_ROOT)/packages/opencode-router/Dockerfile \ + --tag $(ROUTER_FULL) \ + --tag $(ROUTER_IMAGE):latest \ + --push \ + $(MONOREPO_ROOT) + @echo "✓ Pushed $(ROUTER_FULL)" + +push-operator: _ghcr-login + @echo "→ Pushing $(OPERATOR_FULL) ..." + docker buildx build \ + --platform linux/amd64 \ + --file $(DEPLOY_DIR)/opencode-cloudflare-operator/Dockerfile \ + --tag $(OPERATOR_FULL) \ + --tag $(OPERATOR_IMAGE):latest \ + --push \ + $(DEPLOY_DIR)/opencode-cloudflare-operator + @echo "✓ Pushed $(OPERATOR_FULL)" + +push: push-router push-operator + +build-push: build push + +# ------------------------------------------------------------------------------ +# Pulumi deployment +# ------------------------------------------------------------------------------ + +# preview — dry-run, no changes applied +preview: + cd $(PULUMI_DIR) && pulumi preview --stack $(STACK_NAME) --diff + +# deploy — updates image config then runs pulumi up +deploy: + @echo "→ Updating Pulumi image config ..." + cd $(PULUMI_DIR) && \ + pulumi config set --stack $(STACK_NAME) code:routerImage "$(ROUTER_FULL)" && \ + pulumi config set --stack $(STACK_NAME) code:cfOperatorImage "$(OPERATOR_FULL)" + @echo "→ Deploying stack $(STACK_NAME) ..." + cd $(PULUMI_DIR) && pulumi up --stack $(STACK_NAME) --yes + @echo "✓ Deployed" + +# release — the full cycle: build, push, deploy +release: build-push deploy + +# destroy — tears down all cluster resources +destroy: + @echo "WARNING: This will PERMANENTLY destroy all resources in stack '$(STACK_NAME)'." + @echo " This cannot be undone." + @printf "Type the stack name to confirm [$(STACK_NAME)]: " && read ans && [ "$$ans" = "$(STACK_NAME)" ] || (echo "Aborted." && exit 1) + cd $(PULUMI_DIR) && pulumi destroy --stack $(STACK_NAME) + +# logs — recent deployment history +logs: + cd $(PULUMI_DIR) && pulumi stack history --stack $(STACK_NAME) --page-size 10 diff --git a/deployment/homelab/Pulumi.dev.yaml b/deployment/homelab/Pulumi.dev.yaml new file mode 100644 index 000000000000..9398d8fb26f9 --- /dev/null +++ b/deployment/homelab/Pulumi.dev.yaml @@ -0,0 +1,9 @@ +config: + code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-local.af5c3ddb2 + code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-local.af5c3ddb2 + code:opencodeImage: ghcr.io/mrsimpson/opencode:1.2.27-homelab.6 + code:homelabStack: mrsimpson/homelab/dev + code:anthropicApiKey: + secure: AAABAG/9eXalNNef/hxPr5yv8F9phJnv6c/p7GVpY8Rnutzox59WLEkwutX7RtF7djYUqqXHf7jRoR7PYDkf0Kx1dr8OF+coy/u3i/S+Cz3DpQzfwQrhRCyx/AtSC3AIUiteAfzlxK/UDAse2MabbOuk8W733qZVhFuBDkqxYgm3Du9kFZD97anwz1Y= + cloudflare:apiToken: + secure: AAABAHpX+bs29JxwbLtL7Cu+4PeyLIFW8m3sAmwgIPsTJvuiynbDXfOa05NYvVTZBjwuVaiRuwtq4t3qhXFipKRKmGRU4c7q diff --git a/deployment/homelab/Pulumi.yaml b/deployment/homelab/Pulumi.yaml new file mode 100644 index 000000000000..bdb60810c7ec --- /dev/null +++ b/deployment/homelab/Pulumi.yaml @@ -0,0 +1,24 @@ +name: opencode +description: opencode-router deployment — per-user isolated OpenCode instances on homelab k8s +runtime: + name: nodejs + options: + typescript: true +main: src/ + +config: + # All values use the "code:" namespace prefix (matching the pulumi.Config("code") in src/index.ts) + # Set with: pulumi config set code: + + # Stack reference to homelab base stack (provides tunnelCname, cloudflareZoneId, domain) + code:homelabStack: + value: mrsimpson/homelab/dev + + # Image references — update when new images are built + # pulumi config set code:routerImage ghcr.io/mrsimpson/opencode-router: + # pulumi config set code:cfOperatorImage ghcr.io/mrsimpson/opencode-cloudflare-operator: + # pulumi config set code:opencodeImage ghcr.io/mrsimpson/opencode: + + # Secrets (set with --secret flag): + # pulumi config set code:anthropicApiKey --secret + # pulumi config set cloudflare:apiToken --secret diff --git a/deployment/homelab/README.md b/deployment/homelab/README.md new file mode 100644 index 000000000000..5d22d3452795 --- /dev/null +++ b/deployment/homelab/README.md @@ -0,0 +1,104 @@ +# opencode homelab deployment + +This directory contains the [Pulumi](https://www.pulumi.com/) stack that deploys opencode to the [homelab Kubernetes cluster](https://github.com/mrsimpson/homelab). + +## What this deploys + +- **opencode-router** — HTTP router that spins up per-user opencode pods on demand and routes sessions to them +- **opencode-cloudflare-operator** sidecar — watches session pods and dynamically creates Cloudflare tunnel DNS records + Traefik IngressRoutes so each session is reachable at `-oc.` +- Supporting Kubernetes resources: Namespace, RBAC, Secrets, ConfigMap, ExternalSecret (GHCR pull credentials), Cloudflare DNS CNAME for the main `code.` entry point + +Authentication is handled by the shared [oauth2-proxy](https://github.com/oauth2-proxy/oauth2-proxy) deployment in the cluster (GitHub OAuth). + +## Why this directory is at the monorepo root (not inside `packages/`) + +The opencode monorepo uses [bun](https://bun.sh/) workspaces. Bun's workspace `catalog:` protocol is not supported by npm/Node.js tooling, which means any Pulumi stack (which runs with `node`, not `bun`) placed inside `packages/` would fail to install its dependencies. + +Keeping this directory at `homelab/` (outside the bun workspace glob `packages/*`) means it is a plain npm package that npm, Node.js, and Pulumi can handle without modification. It is intentionally **not** part of the opencode bun workspace. + +## How it works + +``` +opencode repo (this repo) homelab repo +────────────────────────── ────────────────────── +packages/opencode-router/ ───► ghcr.io/mrsimpson/opencode-router +packages/opencode-cloudflare- ghcr.io/mrsimpson/opencode-cloudflare-operator + operator/ ───► + +homelab/ github.com/mrsimpson/homelab + src/index.ts ─── reads ──────► StackReference outputs (tunnelCname, zoneId, domain) + ─── npm ──────────► @mrsimpson/homelab-core-components (npmjs.com) + │ + └─ createHomelabContextFromStack() + ExposedWebApp (Deployment + Service + IngressRoutes + DNS) +``` + +The `homelab/` stack: +1. References the homelab base stack via [Pulumi StackReference](https://www.pulumi.com/docs/concepts/stack/#stackreferences) to get shared infrastructure facts (Cloudflare tunnel CNAME, zone ID, domain) +2. Uses [`@mrsimpson/homelab-core-components`](https://www.npmjs.com/package/@mrsimpson/homelab-core-components) — a published npm package from the homelab repo — to deploy the router as an `ExposedWebApp` (Traefik OAuth2-Proxy routes, Cloudflare DNS, Pod Security Standards, etc.) +3. Adds the cloudflare-operator as an `extraContainer` sidecar on the router pod + +## Prerequisites + +- [Pulumi CLI](https://www.pulumi.com/docs/install/) +- [Docker](https://docs.docker.com/get-docker/) with buildx (for local image builds) +- Node.js ≥ 24 +- `PULUMI_ACCESS_TOKEN` — Pulumi Cloud token with access to the `mrsimpson` org +- `KUBECONFIG` — kubeconfig scoped to the `code` namespace on the homelab cluster +- `GITHUB_PAT` — GitHub PAT with `write:packages` scope (for pushing images to GHCR) + +## Local development workflow + +```bash +# First time: install dependencies +npm install + +# See all available targets +make help + +# Build both Docker images locally +make build + +# Build + push images to GHCR + deploy to cluster (full release cycle) +make release + +# Or step by step: +make build-push # build and push images +make deploy # update Pulumi image config + pulumi up + +# Dry-run: see what would change without applying +make preview + +# Tear down everything +make destroy +``` + +The `deploy` target automatically updates the Pulumi stack config with the newly built image tags before running `pulumi up`. Image tags follow the pattern `-local.` (e.g. `0.0.1-local.a3f1c2d`). + +## Pulumi stack config + +Stack config lives in `Pulumi.dev.yaml`. Secrets are encrypted by Pulumi. + +| Key | Description | +|---|---| +| `code:routerImage` | opencode-router container image (tag updated by `make deploy`) | +| `code:cfOperatorImage` | cloudflare-operator container image | +| `code:opencodeImage` | opencode session pod image | +| `code:anthropicApiKey` | Anthropic API key (secret) | +| `code:homelabStack` | Pulumi StackReference to the homelab base stack | +| `cloudflare:apiToken` | Cloudflare API token (secret) | + +## CI/CD + +Images are built and pushed automatically by GitHub Actions: +- [`build-opencode-router.yml`](../.github/workflows/build-opencode-router.yml) — triggers on changes to `packages/opencode-router/` +- [`build-cloudflare-operator.yml`](../.github/workflows/build-cloudflare-operator.yml) — triggers on changes to `packages/opencode-cloudflare-operator/` +- [`deploy-homelab.yml`](../.github/workflows/deploy-homelab.yml) — runs `pulumi up` after the operator image build succeeds + +## Related + +- [homelab repo](https://github.com/mrsimpson/homelab) — the base cluster infrastructure +- [`@mrsimpson/homelab-core-components`](https://www.npmjs.com/package/@mrsimpson/homelab-core-components) — the published Pulumi component library used by this stack +- [opencode-cloudflare-operator](../packages/opencode-cloudflare-operator/) — the operator sidecar source code +- [opencode-router](../packages/opencode-router/) — the router source code +- [ADR-013: External App Deployment](https://github.com/mrsimpson/homelab/blob/main/docs/adr/013-external-app-deployment-with-published-packages-and-esc.md) — the architectural decision record explaining this approach diff --git a/deployment/homelab/package-lock.json b/deployment/homelab/package-lock.json new file mode 100644 index 000000000000..1f42c4b1f674 --- /dev/null +++ b/deployment/homelab/package-lock.json @@ -0,0 +1,5224 @@ +{ + "name": "@opencode-ai/homelab", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@opencode-ai/homelab", + "version": "0.1.0", + "dependencies": { + "@mrsimpson/homelab-core-components": "^0.1.0", + "@pulumi/kubernetes": "^4.18.3", + "@pulumi/pulumi": "^3.137.0" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "typescript": "^5.7.0" + } + }, + "node_modules/@gar/promise-retry": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", + "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", + "license": "ISC" + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@logdna/tail-file": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@logdna/tail-file/-/tail-file-2.2.0.tgz", + "integrity": "sha512-XGSsWDweP80Fks16lwkAUIr54ICyBs6PsI4mpfTLQaWgEJRtY9xEV+PeyDpJ+sJEGZxqINlpmAwe/6tS1pP8Ng==", + "license": "SEE LICENSE IN LICENSE", + "engines": { + "node": ">=10.3.0" + } + }, + "node_modules/@mrsimpson/homelab-core-components": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mrsimpson/homelab-core-components/-/homelab-core-components-0.1.0.tgz", + "integrity": "sha512-/E+ltu5YJcbShA5IpJUo56GxjYyvUqNW0WDbvcrXmfOguVOfiAY4DpltD3++LCSTtO9Mt6AhnVwqgl8qe/9W1A==", + "license": "MIT", + "engines": { + "node": ">=24.0.0" + }, + "peerDependencies": { + "@pulumi/cloudflare": "^5.42.0", + "@pulumi/kubernetes": "^4.18.3", + "@pulumi/pulumi": "^3.137.0" + } + }, + "node_modules/@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/arborist": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-9.4.2.tgz", + "integrity": "sha512-omJgPyzt11cEGrxzgrECoOyxAunmPMgBFTcAB/FbaB+9iOYhGmRdsQqySV8o0LWQ/l2kTeASUIMR4xJufVwmtw==", + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", + "common-ancestor-path": "^2.0.0", + "hosted-git-info": "^9.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "semver": "^7.3.7", + "ssri": "^13.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^4.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", + "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git/node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", + "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", + "license": "ISC", + "dependencies": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-5.0.3.tgz", + "integrity": "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw==", + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/metavuln-calculator": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-9.0.3.tgz", + "integrity": "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg==", + "license": "ISC", + "dependencies": { + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-4.0.0.tgz", + "integrity": "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.5.tgz", + "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", + "license": "ISC", + "dependencies": { + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-5.0.0.tgz", + "integrity": "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ==", + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz", + "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", + "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", + "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", + "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.57.2.tgz", + "integrity": "sha512-gHU1vA3JnHbNxEXg5iysqCWxN9j83d7/epTYBZflqQnTyCC4N7yZXn/dMM+bEmyhQPGjhCkNZLx4vZuChH1PYw==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-exporter-base": "0.57.2", + "@opentelemetry/otlp-grpc-exporter-base": "0.57.2", + "@opentelemetry/otlp-transformer": "0.57.2", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.30.1.tgz", + "integrity": "sha512-6S2QIMJahIquvFaaxmcwpvQQRD/YFaMTNoIxrfPIPOeITN+a8lfEcPDxNxn8JDAaxkg+4EnXhz8upVDYenoQjA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", + "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.57.2", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-grpc": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.57.2.tgz", + "integrity": "sha512-TR6YQA67cLSZzdxbf2SrbADJy2Y8eBW1+9mF15P0VK2MYcpdoUSmQTF1oMkBwa3B9NwqDFA2fq7wYTTutFQqaQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "0.57.2", + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.57.2.tgz", + "integrity": "sha512-XdxEzL23Urhidyebg5E6jZoaiW5ygP/mRjxLHixogbqwDy2Faduzb5N0o/Oi+XTIJu+iyxXdVORjXax+Qgfxag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-transformer": "0.57.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.57.2.tgz", + "integrity": "sha512-USn173KTWy0saqqRB5yU9xUZ2xdgb1Rdu5IosJnm9aV4hMTuFFRTUsQxbgc24QxpCHeoKzzCSnS/JzdV0oM2iQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-exporter-base": "0.57.2", + "@opentelemetry/otlp-transformer": "0.57.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.57.2.tgz", + "integrity": "sha512-48IIRj49gbQVK52jYsw70+Jv+JbahT8BqT2Th7C4H7RCM9d0gZ5sgNPoMpWldmfjvIsSgiGJtjfk9MeZvjhoig==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.57.2", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-logs": "0.57.2", + "@opentelemetry/sdk-metrics": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1", + "protobufjs": "^7.3.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/propagator-b3": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.30.1.tgz", + "integrity": "sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.30.1.tgz", + "integrity": "sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", + "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.57.2.tgz", + "integrity": "sha512-TXFHJ5c+BKggWbdEQ/inpgIzEmS2BGQowLE9UhsMd7YYlUfBQJ4uax0VF/B5NYigdM/75OoJGhAV3upEhK+3gg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.57.2", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", + "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", + "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.30.1.tgz", + "integrity": "sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "1.30.1", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/propagator-b3": "1.30.1", + "@opentelemetry/propagator-jaeger": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@pulumi/cloudflare": { + "version": "5.49.1", + "resolved": "https://registry.npmjs.org/@pulumi/cloudflare/-/cloudflare-5.49.1.tgz", + "integrity": "sha512-sc4j3XgKId9g9hIB5ZS4QXCLStZzYwzIAgbeAfW4+O78Nd3/tkNsuEWmUnPTpsw5Ezpc5zIwZxBCwhPX5qg+sA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@pulumi/pulumi": "^3.142.0" + } + }, + "node_modules/@pulumi/kubernetes": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@pulumi/kubernetes/-/kubernetes-4.28.0.tgz", + "integrity": "sha512-tdoUN7blJv1rrwr+2Pc55CP/+L1sMkH3z5Y7DI2yfNiGgdeaOHYia+hYsnLgR4HtBSfIfuBVWxGkZwGWOgUmOQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@pulumi/pulumi": "^3.142.0", + "glob": "^12.0.0", + "shell-quote": "^1.6.1" + } + }, + "node_modules/@pulumi/pulumi": { + "version": "3.230.0", + "resolved": "https://registry.npmjs.org/@pulumi/pulumi/-/pulumi-3.230.0.tgz", + "integrity": "sha512-wWHP65RKj2zz8R8N4sImPie4bQM/walrCOeNUl46TkdHOOihFdg6cMO2l1IPufh/EBBLtiUBrzpP0wi8OWqhsQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.10.1", + "@logdna/tail-file": "^2.0.6", + "@npmcli/arborist": "^9.0.0", + "@opentelemetry/api": "^1.9", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.57", + "@opentelemetry/exporter-zipkin": "^1.30", + "@opentelemetry/instrumentation": "^0.57", + "@opentelemetry/instrumentation-grpc": "^0.57", + "@opentelemetry/resources": "^1.30", + "@opentelemetry/sdk-trace-base": "^1.30", + "@opentelemetry/sdk-trace-node": "^1.30", + "@types/google-protobuf": "^3.15.5", + "@types/semver": "^7.5.6", + "@types/tmp": "^0.2.6", + "execa": "^5.1.0", + "fdir": "^6.5.0", + "google-protobuf": "^3.21.4", + "got": "^11.8.6", + "ini": "^2.0.0", + "js-yaml": "^3.14.2", + "minimist": "^1.2.6", + "normalize-package-data": "^6.0.0", + "package-directory": "^8.1.0", + "picomatch": "^4.0.0", + "require-from-string": "^2.0.1", + "semver": "^7.5.2", + "source-map-support": "^0.5.6", + "tmp": "^0.2.4", + "upath": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "ts-node": ">= 7.0.1 < 12", + "typescript": ">= 3.8.3 < 7" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@sigstore/bundle": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.2.0.tgz", + "integrity": "sha512-kxHrDQ9YgfrWUSXU0cjsQGv8JykOFZQ9ErNKbFPWzk3Hgpwu8x2hHrQ9IdA8yl+j9RTLTC3sAF3Tdq1IQCP4oA==", + "license": "Apache-2.0", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.1.tgz", + "integrity": "sha512-/ScWUhhoFasJsSRGTVBwId1loQjjnjAfE4djL6ZhrXRpNCmPTnUKF5Jokd58ILseOMjzET3UrMOtJPS9sYeI0g==", + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.1.tgz", + "integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==", + "license": "Apache-2.0", + "dependencies": { + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.2.tgz", + "integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.0.tgz", + "integrity": "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", + "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/google-protobuf": { + "version": "3.15.12", + "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.12.tgz", + "integrity": "sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ==", + "license": "MIT" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "license": "MIT" + }, + "node_modules/@types/shimmer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", + "license": "MIT" + }, + "node_modules/@types/tmp": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz", + "integrity": "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==", + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bin-links": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-6.0.0.tgz", + "integrity": "sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w==", + "license": "ISC", + "dependencies": { + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/cacache": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", + "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cmd-shim": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-8.0.0.tgz", + "integrity": "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-12.0.0.tgz", + "integrity": "sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-protobuf": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz", + "integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==", + "license": "(BSD-3-Clause AND Apache-2.0)" + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore-walk": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", + "license": "ISC", + "dependencies": { + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/import-in-the-middle": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", + "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/json-stringify-nice": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", + "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/just-diff": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", + "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==", + "license": "MIT" + }, + "node_modules/just-diff-apply": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz", + "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==", + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz", + "integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/make-fetch-happen": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz", + "integrity": "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==", + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", + "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-gyp": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", + "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-bundled": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", + "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-install-checks": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-package-arg": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.2.tgz", + "integrity": "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==", + "license": "ISC", + "dependencies": { + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-packlist": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.4.tgz", + "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", + "license": "ISC", + "dependencies": { + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", + "license": "ISC", + "dependencies": { + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^4.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-directory": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/package-directory/-/package-directory-8.2.0.tgz", + "integrity": "sha512-qJSu5Mo6tHmRxCy2KCYYKYgcfBdUpy9dwReaZD/xwf608AUk/MoRtIOWzgDtUeGeC7n/55yC3MI1Q+MbSoektw==", + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "21.5.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.5.0.tgz", + "integrity": "sha512-VtZ0SB8mb5Tzw3dXDfVAIjhyVKUHZkS/ZH9/5mpKenwC9sFOXNI0JI7kEF7IMkwOnsWMFrvAZHzx1T5fmrp9FQ==", + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/parse-conflict-json": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-5.0.1.tgz", + "integrity": "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ==", + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^5.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/proggy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/proggy/-/proggy-4.0.0.tgz", + "integrity": "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/promise-all-reject-late": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", + "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/promise-call-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.2.tgz", + "integrity": "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==", + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-cmd-shim": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-6.0.0.tgz", + "integrity": "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "optional": true + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", + "license": "BSD-2-Clause" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/sigstore": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.0.tgz", + "integrity": "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "license": "CC0-1.0" + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/ssri": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/treeverse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz", + "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/tuf-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", + "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", + "license": "MIT", + "dependencies": { + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.1.tgz", + "integrity": "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==", + "license": "ISC", + "dependencies": { + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + }, + "dependencies": { + "@gar/promise-retry": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", + "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==" + }, + "@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "requires": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + } + }, + "@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "requires": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + } + }, + "@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==" + }, + "@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "requires": { + "minipass": "^7.0.4" + } + }, + "@isaacs/string-locale-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==" + }, + "@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==" + }, + "@logdna/tail-file": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@logdna/tail-file/-/tail-file-2.2.0.tgz", + "integrity": "sha512-XGSsWDweP80Fks16lwkAUIr54ICyBs6PsI4mpfTLQaWgEJRtY9xEV+PeyDpJ+sJEGZxqINlpmAwe/6tS1pP8Ng==" + }, + "@mrsimpson/homelab-core-components": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mrsimpson/homelab-core-components/-/homelab-core-components-0.1.0.tgz", + "integrity": "sha512-/E+ltu5YJcbShA5IpJUo56GxjYyvUqNW0WDbvcrXmfOguVOfiAY4DpltD3++LCSTtO9Mt6AhnVwqgl8qe/9W1A==", + "requires": {} + }, + "@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "requires": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + } + }, + "@npmcli/arborist": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-9.4.2.tgz", + "integrity": "sha512-omJgPyzt11cEGrxzgrECoOyxAunmPMgBFTcAB/FbaB+9iOYhGmRdsQqySV8o0LWQ/l2kTeASUIMR4xJufVwmtw==", + "requires": { + "@gar/promise-retry": "^1.0.0", + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", + "common-ancestor-path": "^2.0.0", + "hosted-git-info": "^9.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "semver": "^7.3.7", + "ssri": "^13.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^4.0.0" + } + }, + "@npmcli/fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "requires": { + "semver": "^7.3.5" + } + }, + "@npmcli/git": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", + "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", + "requires": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "which": "^6.0.0" + }, + "dependencies": { + "ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==" + } + } + }, + "@npmcli/installed-package-contents": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", + "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", + "requires": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" + } + }, + "@npmcli/map-workspaces": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-5.0.3.tgz", + "integrity": "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw==", + "requires": { + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" + }, + "dependencies": { + "glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "requires": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + } + } + } + }, + "@npmcli/metavuln-calculator": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-9.0.3.tgz", + "integrity": "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg==", + "requires": { + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5" + } + }, + "@npmcli/name-from-folder": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-4.0.0.tgz", + "integrity": "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg==" + }, + "@npmcli/node-gyp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==" + }, + "@npmcli/package-json": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.5.tgz", + "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", + "requires": { + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" + }, + "dependencies": { + "glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "requires": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + } + } + } + }, + "@npmcli/promise-spawn": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", + "requires": { + "which": "^6.0.0" + } + }, + "@npmcli/query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-5.0.0.tgz", + "integrity": "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ==", + "requires": { + "postcss-selector-parser": "^7.0.0" + } + }, + "@npmcli/redact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==" + }, + "@npmcli/run-script": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz", + "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", + "requires": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" + } + }, + "@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==" + }, + "@opentelemetry/api-logs": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", + "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/context-async-hooks": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", + "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", + "requires": {} + }, + "@opentelemetry/core": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", + "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", + "requires": { + "@opentelemetry/semantic-conventions": "1.28.0" + } + }, + "@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.57.2.tgz", + "integrity": "sha512-gHU1vA3JnHbNxEXg5iysqCWxN9j83d7/epTYBZflqQnTyCC4N7yZXn/dMM+bEmyhQPGjhCkNZLx4vZuChH1PYw==", + "requires": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-exporter-base": "0.57.2", + "@opentelemetry/otlp-grpc-exporter-base": "0.57.2", + "@opentelemetry/otlp-transformer": "0.57.2", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1" + } + }, + "@opentelemetry/exporter-zipkin": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.30.1.tgz", + "integrity": "sha512-6S2QIMJahIquvFaaxmcwpvQQRD/YFaMTNoIxrfPIPOeITN+a8lfEcPDxNxn8JDAaxkg+4EnXhz8upVDYenoQjA==", + "requires": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", + "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", + "requires": { + "@opentelemetry/api-logs": "0.57.2", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + }, + "@opentelemetry/instrumentation-grpc": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.57.2.tgz", + "integrity": "sha512-TR6YQA67cLSZzdxbf2SrbADJy2Y8eBW1+9mF15P0VK2MYcpdoUSmQTF1oMkBwa3B9NwqDFA2fq7wYTTutFQqaQ==", + "requires": { + "@opentelemetry/instrumentation": "0.57.2", + "@opentelemetry/semantic-conventions": "1.28.0" + } + }, + "@opentelemetry/otlp-exporter-base": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.57.2.tgz", + "integrity": "sha512-XdxEzL23Urhidyebg5E6jZoaiW5ygP/mRjxLHixogbqwDy2Faduzb5N0o/Oi+XTIJu+iyxXdVORjXax+Qgfxag==", + "requires": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-transformer": "0.57.2" + } + }, + "@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.57.2.tgz", + "integrity": "sha512-USn173KTWy0saqqRB5yU9xUZ2xdgb1Rdu5IosJnm9aV4hMTuFFRTUsQxbgc24QxpCHeoKzzCSnS/JzdV0oM2iQ==", + "requires": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/otlp-exporter-base": "0.57.2", + "@opentelemetry/otlp-transformer": "0.57.2" + } + }, + "@opentelemetry/otlp-transformer": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.57.2.tgz", + "integrity": "sha512-48IIRj49gbQVK52jYsw70+Jv+JbahT8BqT2Th7C4H7RCM9d0gZ5sgNPoMpWldmfjvIsSgiGJtjfk9MeZvjhoig==", + "requires": { + "@opentelemetry/api-logs": "0.57.2", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/sdk-logs": "0.57.2", + "@opentelemetry/sdk-metrics": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1", + "protobufjs": "^7.3.0" + } + }, + "@opentelemetry/propagator-b3": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.30.1.tgz", + "integrity": "sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==", + "requires": { + "@opentelemetry/core": "1.30.1" + } + }, + "@opentelemetry/propagator-jaeger": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.30.1.tgz", + "integrity": "sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==", + "requires": { + "@opentelemetry/core": "1.30.1" + } + }, + "@opentelemetry/resources": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", + "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", + "requires": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" + } + }, + "@opentelemetry/sdk-logs": { + "version": "0.57.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.57.2.tgz", + "integrity": "sha512-TXFHJ5c+BKggWbdEQ/inpgIzEmS2BGQowLE9UhsMd7YYlUfBQJ4uax0VF/B5NYigdM/75OoJGhAV3upEhK+3gg==", + "requires": { + "@opentelemetry/api-logs": "0.57.2", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1" + } + }, + "@opentelemetry/sdk-metrics": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", + "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", + "requires": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1" + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", + "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", + "requires": { + "@opentelemetry/core": "1.30.1", + "@opentelemetry/resources": "1.30.1", + "@opentelemetry/semantic-conventions": "1.28.0" + } + }, + "@opentelemetry/sdk-trace-node": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.30.1.tgz", + "integrity": "sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==", + "requires": { + "@opentelemetry/context-async-hooks": "1.30.1", + "@opentelemetry/core": "1.30.1", + "@opentelemetry/propagator-b3": "1.30.1", + "@opentelemetry/propagator-jaeger": "1.30.1", + "@opentelemetry/sdk-trace-base": "1.30.1", + "semver": "^7.5.2" + } + }, + "@opentelemetry/semantic-conventions": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", + "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==" + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "@pulumi/cloudflare": { + "version": "5.49.1", + "resolved": "https://registry.npmjs.org/@pulumi/cloudflare/-/cloudflare-5.49.1.tgz", + "integrity": "sha512-sc4j3XgKId9g9hIB5ZS4QXCLStZzYwzIAgbeAfW4+O78Nd3/tkNsuEWmUnPTpsw5Ezpc5zIwZxBCwhPX5qg+sA==", + "peer": true, + "requires": { + "@pulumi/pulumi": "^3.142.0" + } + }, + "@pulumi/kubernetes": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@pulumi/kubernetes/-/kubernetes-4.28.0.tgz", + "integrity": "sha512-tdoUN7blJv1rrwr+2Pc55CP/+L1sMkH3z5Y7DI2yfNiGgdeaOHYia+hYsnLgR4HtBSfIfuBVWxGkZwGWOgUmOQ==", + "requires": { + "@pulumi/pulumi": "^3.142.0", + "glob": "^12.0.0", + "shell-quote": "^1.6.1" + } + }, + "@pulumi/pulumi": { + "version": "3.230.0", + "resolved": "https://registry.npmjs.org/@pulumi/pulumi/-/pulumi-3.230.0.tgz", + "integrity": "sha512-wWHP65RKj2zz8R8N4sImPie4bQM/walrCOeNUl46TkdHOOihFdg6cMO2l1IPufh/EBBLtiUBrzpP0wi8OWqhsQ==", + "requires": { + "@grpc/grpc-js": "^1.10.1", + "@logdna/tail-file": "^2.0.6", + "@npmcli/arborist": "^9.0.0", + "@opentelemetry/api": "^1.9", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.57", + "@opentelemetry/exporter-zipkin": "^1.30", + "@opentelemetry/instrumentation": "^0.57", + "@opentelemetry/instrumentation-grpc": "^0.57", + "@opentelemetry/resources": "^1.30", + "@opentelemetry/sdk-trace-base": "^1.30", + "@opentelemetry/sdk-trace-node": "^1.30", + "@types/google-protobuf": "^3.15.5", + "@types/semver": "^7.5.6", + "@types/tmp": "^0.2.6", + "execa": "^5.1.0", + "fdir": "^6.5.0", + "google-protobuf": "^3.21.4", + "got": "^11.8.6", + "ini": "^2.0.0", + "js-yaml": "^3.14.2", + "minimist": "^1.2.6", + "normalize-package-data": "^6.0.0", + "package-directory": "^8.1.0", + "picomatch": "^4.0.0", + "require-from-string": "^2.0.1", + "semver": "^7.5.2", + "source-map-support": "^0.5.6", + "tmp": "^0.2.4", + "upath": "^1.1.0" + } + }, + "@sigstore/bundle": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", + "requires": { + "@sigstore/protobuf-specs": "^0.5.0" + } + }, + "@sigstore/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.2.0.tgz", + "integrity": "sha512-kxHrDQ9YgfrWUSXU0cjsQGv8JykOFZQ9ErNKbFPWzk3Hgpwu8x2hHrQ9IdA8yl+j9RTLTC3sAF3Tdq1IQCP4oA==" + }, + "@sigstore/protobuf-specs": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.1.tgz", + "integrity": "sha512-/ScWUhhoFasJsSRGTVBwId1loQjjnjAfE4djL6ZhrXRpNCmPTnUKF5Jokd58ILseOMjzET3UrMOtJPS9sYeI0g==" + }, + "@sigstore/sign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.1.tgz", + "integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==", + "requires": { + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" + } + }, + "@sigstore/tuf": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.2.tgz", + "integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==", + "requires": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" + } + }, + "@sigstore/verify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.0.tgz", + "integrity": "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==", + "requires": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0" + } + }, + "@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" + }, + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==" + }, + "@tufjs/models": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", + "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", + "requires": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.1.1" + } + }, + "@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "@types/google-protobuf": { + "version": "3.15.12", + "resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.12.tgz", + "integrity": "sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ==" + }, + "@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==" + }, + "@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "requires": { + "undici-types": "~7.16.0" + } + }, + "@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "requires": { + "@types/node": "*" + } + }, + "@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==" + }, + "@types/shimmer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==" + }, + "@types/tmp": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz", + "integrity": "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==" + }, + "abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==" + }, + "acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==" + }, + "acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "requires": {} + }, + "agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==" + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" + }, + "bin-links": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-6.0.0.tgz", + "integrity": "sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w==", + "requires": { + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" + } + }, + "brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "requires": { + "balanced-match": "^4.0.2" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "cacache": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", + "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", + "requires": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0" + }, + "dependencies": { + "glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "requires": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + } + } + } + }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" + }, + "cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" + }, + "cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==" + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "cmd-shim": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-8.0.0.tgz", + "integrity": "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==" + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==" + }, + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "requires": { + "ms": "^2.1.3" + } + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "requires": { + "once": "^1.4.0" + } + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==" + }, + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "requires": {} + }, + "find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==" + }, + "foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "requires": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + } + } + }, + "fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "requires": { + "minipass": "^7.0.3" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + }, + "glob": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-12.0.0.tgz", + "integrity": "sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw==", + "requires": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + } + }, + "google-protobuf": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz", + "integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==" + }, + "got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", + "requires": { + "lru-cache": "^11.1.0" + } + }, + "http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==" + }, + "http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, + "https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" + }, + "iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "ignore-walk": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", + "requires": { + "minimatch": "^10.0.3" + } + }, + "import-in-the-middle": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", + "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", + "requires": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, + "ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==" + }, + "ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==" + }, + "is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "requires": { + "hasown": "^2.0.2" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==" + }, + "jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "requires": { + "@isaacs/cliui": "^9.0.0" + } + }, + "js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "json-parse-even-better-errors": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==" + }, + "json-stringify-nice": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", + "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==" + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==" + }, + "just-diff": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", + "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==" + }, + "just-diff-apply": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz", + "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==" + }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "requires": { + "json-buffer": "3.0.1" + } + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, + "lru-cache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz", + "integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==" + }, + "make-fetch-happen": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz", + "integrity": "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==", + "requires": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, + "minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "requires": { + "brace-expansion": "^5.0.5" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==" + }, + "minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "requires": { + "minipass": "^7.0.3" + } + }, + "minipass-fetch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", + "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", + "requires": { + "iconv-lite": "^0.7.2", + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + } + }, + "minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "minipass-sized": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "requires": { + "minipass": "^7.1.2" + } + }, + "minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "requires": { + "minipass": "^7.1.2" + } + }, + "module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" + }, + "node-gyp": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", + "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", + "requires": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" + } + }, + "nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "requires": { + "abbrev": "^4.0.0" + } + }, + "normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "requires": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "dependencies": { + "hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "requires": { + "lru-cache": "^10.0.1" + } + }, + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + } + } + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" + }, + "npm-bundled": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", + "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", + "requires": { + "npm-normalize-package-bin": "^5.0.0" + } + }, + "npm-install-checks": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==" + }, + "npm-package-arg": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.2.tgz", + "integrity": "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==", + "requires": { + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^7.0.0" + } + }, + "npm-packlist": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.4.tgz", + "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", + "requires": { + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" + } + }, + "npm-pick-manifest": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", + "requires": { + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", + "semver": "^7.3.5" + } + }, + "npm-registry-fetch": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", + "requires": { + "@npmcli/redact": "^4.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "requires": { + "path-key": "^3.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" + }, + "p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==" + }, + "package-directory": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/package-directory/-/package-directory-8.2.0.tgz", + "integrity": "sha512-qJSu5Mo6tHmRxCy2KCYYKYgcfBdUpy9dwReaZD/xwf608AUk/MoRtIOWzgDtUeGeC7n/55yC3MI1Q+MbSoektw==", + "requires": { + "find-up-simple": "^1.0.0" + } + }, + "package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "pacote": { + "version": "21.5.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.5.0.tgz", + "integrity": "sha512-VtZ0SB8mb5Tzw3dXDfVAIjhyVKUHZkS/ZH9/5mpKenwC9sFOXNI0JI7kEF7IMkwOnsWMFrvAZHzx1T5fmrp9FQ==", + "requires": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" + } + }, + "parse-conflict-json": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-5.0.1.tgz", + "integrity": "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ==", + "requires": { + "json-parse-even-better-errors": "^5.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "requires": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + } + }, + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" + }, + "postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==" + }, + "proggy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/proggy/-/proggy-4.0.0.tgz", + "integrity": "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ==" + }, + "promise-all-reject-late": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", + "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==" + }, + "promise-call-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.2.tgz", + "integrity": "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==" + }, + "protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + } + }, + "pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, + "read-cmd-shim": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-6.0.0.tgz", + "integrity": "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==" + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, + "require-in-the-middle": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", + "requires": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + } + }, + "resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "requires": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, + "responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "optional": true + }, + "semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==" + }, + "shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "sigstore": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.0.tgz", + "integrity": "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==", + "requires": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.1.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" + } + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, + "socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "requires": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "requires": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + }, + "dependencies": { + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + } + } + }, + "spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==" + }, + "spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "ssri": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "requires": { + "minipass": "^7.0.3" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "tar": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", + "requires": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + } + }, + "tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "requires": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + } + }, + "tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==" + }, + "treeverse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz", + "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==" + }, + "tuf-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", + "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", + "requires": { + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" + } + }, + "typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true + }, + "undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==" + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + }, + "dependencies": { + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + } + } + }, + "validate-npm-package-name": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==" + }, + "walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==" + }, + "which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "requires": { + "isexe": "^4.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "write-file-atomic": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.1.tgz", + "integrity": "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==", + "requires": { + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + } + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==" + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + } + } +} diff --git a/deployment/homelab/package.json b/deployment/homelab/package.json new file mode 100644 index 000000000000..20e7debe12ae --- /dev/null +++ b/deployment/homelab/package.json @@ -0,0 +1,20 @@ +{ + "name": "@opencode-ai/homelab", + "version": "0.1.0", + "private": true, + "description": "Pulumi stack — deploys opencode-router to the homelab Kubernetes cluster", + "main": "src/index.ts", + "scripts": { + "type-check": "tsc --noEmit", + "build": "tsc" + }, + "dependencies": { + "@mrsimpson/homelab-core-components": "^0.1.0", + "@pulumi/kubernetes": "^4.18.3", + "@pulumi/pulumi": "^3.137.0" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "typescript": "^5.7.0" + } +} diff --git a/deployment/homelab/src/index.ts b/deployment/homelab/src/index.ts new file mode 100644 index 000000000000..03a8eaad54bf --- /dev/null +++ b/deployment/homelab/src/index.ts @@ -0,0 +1,395 @@ +import * as pulumi from "@pulumi/pulumi"; +import * as k8s from "@pulumi/kubernetes"; +import { + AuthType, + createHomelabContextFromStack, +} from "@mrsimpson/homelab-core-components"; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const APP_NAME = "code"; +const NAMESPACE = APP_NAME; +const ROUTER_PORT = 3000; +const OPENCODE_PORT = 4096; +/** Suffix appended to hash for session hostnames: -oc. */ +const ROUTE_SUFFIX = "-oc"; +/** In-cluster URL the Cloudflare operator routes session traffic to */ +const ROUTER_SERVICE_URL = `http://${APP_NAME}.${NAMESPACE}.svc.cluster.local:80`; +const CF_OPERATOR_CONTAINER_NAME = "cloudflare-operator"; + +// --------------------------------------------------------------------------- +// Config +// --------------------------------------------------------------------------- + +const cfg = new pulumi.Config("code"); +const cloudflareConfig = new pulumi.Config("cloudflare"); + +// StackReference to the homelab base stack — provides tunnelCname, cloudflareZoneId, domain +const homelabStackName = cfg.get("homelabStack") ?? "mrsimpson/homelab/dev"; +const homelabStack = new pulumi.StackReference(homelabStackName); + +// Read infrastructure facts from homelab stack outputs +const domain = homelabStack.getOutput("domain") as pulumi.Output; +const cfZoneId = homelabStack.getOutput("cloudflareZoneId") as pulumi.Output; + +// Tunnel ID — needed by the Cloudflare operator sidecar. +// Exported from homelab stack as "tunnelId". +const cfTunnelId = homelabStack.getOutput("tunnelId") as pulumi.Output; + +// Build HomelabContext from StackReference — homelab defaults apply +const homelab = createHomelabContextFromStack(homelabStack); + +// --------------------------------------------------------------------------- +// App config +// --------------------------------------------------------------------------- + +const routerImage = cfg.require("routerImage"); +const cfOperatorImage = cfg.require("cfOperatorImage"); +const opencodeImage = cfg.require("opencodeImage"); +const anthropicApiKey = cfg.requireSecret("anthropicApiKey"); +const defaultGitRepo = cfg.get("defaultGitRepo"); +const storageSize = cfg.get("storageSize") ?? "2Gi"; +const cfApiToken = cloudflareConfig.requireSecret("apiToken"); + +// --------------------------------------------------------------------------- +// 1. Namespace (pre-created; passed to ExposedWebApp so it doesn't re-create) +// --------------------------------------------------------------------------- + +const ns = new k8s.core.v1.Namespace(`${APP_NAME}-ns`, { + metadata: { + name: NAMESPACE, + labels: { + app: APP_NAME, + "pod-security.kubernetes.io/enforce": "restricted", + "pod-security.kubernetes.io/enforce-version": "latest", + "pod-security.kubernetes.io/warn": "restricted", + "pod-security.kubernetes.io/warn-version": "latest", + }, + }, +}); + +// --------------------------------------------------------------------------- +// 2. RBAC — router manages user pods/PVCs; operator sidecar watches pods and +// manages IngressRoutes at runtime. +// --------------------------------------------------------------------------- + +const serviceAccount = new k8s.core.v1.ServiceAccount( + `${APP_NAME}-sa`, + { + metadata: { + name: APP_NAME, + namespace: NAMESPACE, + labels: { app: APP_NAME }, + }, + }, + { dependsOn: [ns] }, +); + +const role = new k8s.rbac.v1.Role( + `${APP_NAME}-role`, + { + metadata: { + name: APP_NAME, + namespace: NAMESPACE, + labels: { app: APP_NAME }, + }, + rules: [ + { + apiGroups: [""], + resources: ["pods"], + verbs: ["get", "list", "watch", "create", "delete", "patch"], + }, + { + apiGroups: [""], + resources: ["persistentvolumeclaims"], + verbs: ["get", "list", "create"], + }, + { + apiGroups: ["traefik.io"], + resources: ["ingressroutes"], + verbs: ["get", "list", "create", "delete"], + }, + ], + }, + { dependsOn: [ns] }, +); + +const roleBinding = new k8s.rbac.v1.RoleBinding( + `${APP_NAME}-rolebinding`, + { + metadata: { + name: APP_NAME, + namespace: NAMESPACE, + labels: { app: APP_NAME }, + }, + roleRef: { + apiGroup: "rbac.authorization.k8s.io", + kind: "Role", + name: APP_NAME, + }, + subjects: [ + { + kind: "ServiceAccount", + name: APP_NAME, + namespace: NAMESPACE, + }, + ], + }, + { dependsOn: [role, serviceAccount] }, +); + +// --------------------------------------------------------------------------- +// 3. Secret — Anthropic API key mounted into session pods +// --------------------------------------------------------------------------- + +const apiKeysSecret = new k8s.core.v1.Secret( + `${APP_NAME}-api-keys`, + { + metadata: { + name: "opencode-api-keys", + namespace: NAMESPACE, + labels: { app: APP_NAME }, + }, + type: "Opaque", + stringData: { + ANTHROPIC_API_KEY: anthropicApiKey, + }, + }, + { dependsOn: [ns] }, +); + +// --------------------------------------------------------------------------- +// 4. ConfigMap — opencode.json for session pods +// --------------------------------------------------------------------------- + +const configMap = new k8s.core.v1.ConfigMap( + `${APP_NAME}-config`, + { + metadata: { + name: "opencode-config-dir", + namespace: NAMESPACE, + labels: { app: APP_NAME }, + }, + data: { + "opencode.json": JSON.stringify( + { + $schema: "https://opencode.ai/config.json", + model: "anthropic/claude-sonnet-4-5", + }, + null, + 2, + ), + }, + }, + { dependsOn: [ns] }, +); + +// --------------------------------------------------------------------------- +// 5. ExternalSecret — GHCR pull secret +// Explicitly created because ExposedWebApp only auto-creates it when it +// creates the namespace. Since we pre-create the namespace and pass it in, +// we must create this manually. +// --------------------------------------------------------------------------- + +const pullSecret = new k8s.apiextensions.CustomResource( + `${APP_NAME}-ghcr-pull-secret`, + { + apiVersion: "external-secrets.io/v1beta1", + kind: "ExternalSecret", + metadata: { + name: "ghcr-pull-secret", + namespace: NAMESPACE, + labels: { app: APP_NAME }, + }, + spec: { + refreshInterval: "1h", + secretStoreRef: { + name: "pulumi-esc", + kind: "ClusterSecretStore", + }, + target: { + name: "ghcr-pull-secret", + creationPolicy: "Owner", + template: { + type: "kubernetes.io/dockerconfigjson", + engineVersion: "v2", + data: { + ".dockerconfigjson": `{"auths":{"ghcr.io":{"username":"{{ .github_username }}","password":"{{ .github_token }}","auth":"{{ printf "%s:%s" .github_username .github_token | b64enc }}"}}}`, + }, + }, + }, + data: [ + { + secretKey: "github_username", + remoteRef: { key: "github-username" }, + }, + { + secretKey: "github_token", + remoteRef: { key: "github-token" }, + }, + ], + }, + }, + { dependsOn: [ns] }, +); + +// --------------------------------------------------------------------------- +// 6. Secret — Cloudflare credentials for the operator sidecar +// --------------------------------------------------------------------------- + +const cfSecret = new k8s.core.v1.Secret( + `${APP_NAME}-cf-credentials`, + { + metadata: { + name: `${APP_NAME}-cf-credentials`, + namespace: NAMESPACE, + labels: { app: APP_NAME }, + }, + type: "Opaque", + stringData: { + CF_API_TOKEN: cfApiToken, + }, + }, + { dependsOn: [ns] }, +); + +// --------------------------------------------------------------------------- +// 7. Cloudflare operator sidecar container spec +// Watches session pods, manages -oc. DNS + tunnel routes. +// --------------------------------------------------------------------------- + +const operatorSidecar = [ + { + name: CF_OPERATOR_CONTAINER_NAME, + image: pulumi.output(cfOperatorImage), + securityContext: { + allowPrivilegeEscalation: false, + runAsNonRoot: true, + capabilities: { drop: ["ALL"] }, + seccompProfile: { type: "RuntimeDefault" }, + }, + env: [ + { name: "WATCH_NAMESPACE", value: NAMESPACE }, + { + name: "POD_LABEL_SELECTOR", + value: "app.kubernetes.io/managed-by=opencode-router", + }, + { name: "CF_ZONE_ID", value: cfZoneId }, + { name: "CF_TUNNEL_ID", value: cfTunnelId }, + { name: "DOMAIN", value: domain }, + { name: "ROUTE_SUFFIX", value: ROUTE_SUFFIX }, + { name: "ROUTER_SERVICE_URL", value: ROUTER_SERVICE_URL }, + { name: "INGRESSROUTE_NAMESPACE", value: NAMESPACE }, + { name: "OAUTH2_CHAIN_MIDDLEWARE", value: `${APP_NAME}-oauth2-chain` }, + { name: "ROUTER_SERVICE_NAME", value: APP_NAME }, + { + name: "CF_API_TOKEN", + valueFrom: { + secretKeyRef: { + name: `${APP_NAME}-cf-credentials`, + key: "CF_API_TOKEN", + }, + }, + }, + ], + readinessProbe: { + httpGet: { path: "/healthz", port: 8080 }, + initialDelaySeconds: 5, + periodSeconds: 10, + }, + livenessProbe: { + httpGet: { path: "/healthz", port: 8080 }, + initialDelaySeconds: 15, + periodSeconds: 30, + }, + resources: { + requests: { cpu: "50m", memory: "64Mi" }, + limits: { cpu: "200m", memory: "128Mi" }, + }, + }, +]; + +// --------------------------------------------------------------------------- +// 8. ExposedWebApp — Deployment, Service, OAuth2-Proxy auth, main DNS CNAME +// --------------------------------------------------------------------------- + +const appDomain = pulumi.interpolate`${APP_NAME}.${domain}`; + +export const app = homelab.createExposedWebApp( + APP_NAME, + { + namespace: ns, + image: pulumi.output(routerImage), + domain: appDomain, + port: ROUTER_PORT, + replicas: 1, + auth: AuthType.OAUTH2_PROXY, + oauth2Proxy: { group: "users" }, + serviceAccountName: APP_NAME, + imagePullSecrets: [{ name: "ghcr-pull-secret" }], + securityContext: { + runAsUser: 1000, + runAsGroup: 1000, + fsGroup: 1000, + }, + resources: { + requests: { cpu: "100m", memory: "128Mi" }, + limits: { cpu: "500m", memory: "256Mi" }, + }, + env: [ + { name: "OPENCODE_IMAGE", value: pulumi.output(opencodeImage) }, + { name: "OPENCODE_NAMESPACE", value: NAMESPACE }, + { name: "OPENCODE_PORT", value: String(OPENCODE_PORT) }, + { name: "STORAGE_CLASS", value: "longhorn-uncritical" }, + { name: "STORAGE_SIZE", value: storageSize }, + { name: "API_KEY_SECRET_NAME", value: "opencode-api-keys" }, + { name: "CONFIG_MAP_NAME", value: "opencode-config-dir" }, + { name: "IMAGE_PULL_SECRET_NAME", value: "ghcr-pull-secret" }, + { name: "ROUTER_DOMAIN", value: domain }, + { name: "ROUTE_SUFFIX", value: ROUTE_SUFFIX }, + ...(defaultGitRepo + ? [{ name: "DEFAULT_GIT_REPO", value: defaultGitRepo }] + : []), + ], + probes: { + readinessProbe: { + httpGet: { + path: "/api/sessions", + port: ROUTER_PORT, + httpHeaders: [ + { name: "X-Auth-Request-Email", value: "healthcheck@probe" }, + ], + }, + initialDelaySeconds: 5, + periodSeconds: 10, + failureThreshold: 3, + }, + livenessProbe: { + httpGet: { + path: "/api/sessions", + port: ROUTER_PORT, + httpHeaders: [ + { name: "X-Auth-Request-Email", value: "healthcheck@probe" }, + ], + }, + initialDelaySeconds: 15, + periodSeconds: 30, + failureThreshold: 3, + }, + }, + extraContainers: operatorSidecar, + tags: ["opencode", "router", "ai"], + }, + { + dependsOn: [roleBinding, pullSecret, cfSecret, apiKeysSecret, configMap], + }, +); + +// --------------------------------------------------------------------------- +// Stack outputs +// --------------------------------------------------------------------------- + +export const url = pulumi.interpolate`https://${appDomain}`; +export const namespace = app.namespace.metadata.name; diff --git a/deployment/homelab/tsconfig.json b/deployment/homelab/tsconfig.json new file mode 100644 index 000000000000..84ba95fef680 --- /dev/null +++ b/deployment/homelab/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "strict": true, + "target": "ES2022", + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*.ts"] +} diff --git a/deployment/opencode-cloudflare-operator/Dockerfile b/deployment/opencode-cloudflare-operator/Dockerfile new file mode 100644 index 000000000000..68126780176d --- /dev/null +++ b/deployment/opencode-cloudflare-operator/Dockerfile @@ -0,0 +1,16 @@ +FROM node:22-alpine AS build +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY tsconfig.json ./ +COPY src/ ./src/ +RUN npm run build + +FROM node:22-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm ci --omit=dev +COPY --from=build /app/dist ./dist +EXPOSE 8080 +USER 1000 +CMD ["node", "dist/index.js"] diff --git a/deployment/opencode-cloudflare-operator/README.md b/deployment/opencode-cloudflare-operator/README.md new file mode 100644 index 000000000000..6b09939a7451 --- /dev/null +++ b/deployment/opencode-cloudflare-operator/README.md @@ -0,0 +1,81 @@ +# opencode-cloudflare-operator + +A lightweight Kubernetes operator that runs as a sidecar in the opencode-router pod. It watches for session pods created by the router and provisions/deprovisions per-session Cloudflare DNS records and Traefik IngressRoutes on demand. + +## Why This Exists + +The opencode-router creates one Kubernetes Pod per (user, repo, branch) session. Each session needs its own public hostname (`-oc.`) so the router can identify which session a request belongs to by reading the `Host` header. + +The `*.` Cloudflare Universal SSL certificate only covers **first-level subdomains** of ``. Session URLs are therefore `-oc.` (dash, not dot) — staying at the first level, covered by the wildcard cert. + +These hostnames cannot be pre-provisioned by Pulumi because they are created at runtime. This operator bridges that gap. + +## What It Does + +On **pod ADDED** (session started): +1. Creates a Cloudflare CNAME DNS record: `-oc.` → `.cfargotunnel.com` +2. Adds a tunnel ingress rule to the Cloudflare Tunnel config: `-oc.` → router service +3. Creates two Traefik IngressRoute resources in the `opencode-router` namespace: + - `opencode-session--oc-signin` — routes `/oauth2/*` to `oauth2-proxy-users` (auth flow) + - `opencode-session--oc-app` — routes `/*` to the `opencode-router` service, protected by the `opencode-router-oauth2-chain` middleware + +On **pod DELETED** (session cleaned up): +1. Deletes the Cloudflare DNS record +2. Removes the tunnel ingress rule +3. Deletes the two IngressRoute resources + +All operations are idempotent — safe to run with multiple operator replicas (2 router replicas = 2 operator sidecars both watching pods). + +## Deployment + +The operator runs as a sidecar container in the opencode-router Deployment. It shares: +- The pod's ServiceAccount (which has RBAC to manage pods and IngressRoutes) +- The pod's imagePullSecrets +- The pod's network namespace + +The Cloudflare API token is injected from a dedicated Secret (`opencode-router-cf-credentials`). + +## Configuration + +All configuration is via environment variables: + +| Variable | Required | Default | Description | +|---|---|---|---| +| `CF_API_TOKEN` | Yes | — | Cloudflare API token (DNS:Edit + Zone:Read + Tunnel:Edit) | +| `CF_ZONE_ID` | Yes | — | Cloudflare Zone ID | +| `CF_TUNNEL_ID` | Yes | — | Cloudflare Tunnel ID | +| `DOMAIN` | Yes | — | Base domain (e.g. `no-panic.org`) | +| `ROUTE_SUFFIX` | No | `""` | Suffix for session hostnames (e.g. `-oc`) | +| `ROUTER_SERVICE_URL` | Yes | — | In-cluster router URL (e.g. `http://opencode-router.opencode-router.svc.cluster.local:80`) | +| `WATCH_NAMESPACE` | No | `opencode-router` | Namespace to watch for session pods | +| `POD_LABEL_SELECTOR` | No | `app.kubernetes.io/managed-by=opencode-router` | Label selector for session pods | +| `INGRESSROUTE_NAMESPACE` | No | `$WATCH_NAMESPACE` | Namespace where IngressRoutes are created | +| `OAUTH2_CHAIN_MIDDLEWARE` | No | `opencode-router-oauth2-chain` | Name of the Traefik middleware chain for OAuth2 auth | +| `ROUTER_SERVICE_NAME` | No | `opencode-router` | Name of the Kubernetes Service for the router | +| `HEALTH_PORT` | No | `8080` | Port for the `/healthz` health check endpoint | + +## Building + +```bash +# From the homelab repo root: +bash images/opencode-cloudflare-operator/build.sh --push --revision + +# Then update Pulumi config: +pulumi config set opencode:cfOperatorImage "ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-homelab." +``` + +## RBAC Requirements + +The ServiceAccount running the operator needs: + +```yaml +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] + - apiGroups: ["traefik.io"] + resources: ["ingressroutes"] + verbs: ["get", "list", "create", "delete"] +``` + +These are provided by the `opencode-router` Role in `packages/apps/opencode-router/src/index.ts`. diff --git a/deployment/opencode-cloudflare-operator/package-lock.json b/deployment/opencode-cloudflare-operator/package-lock.json new file mode 100644 index 000000000000..ccb6fb8418af --- /dev/null +++ b/deployment/opencode-cloudflare-operator/package-lock.json @@ -0,0 +1,848 @@ +{ + "name": "@opencode-ai/opencode-cloudflare-operator", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@opencode-ai/opencode-cloudflare-operator", + "version": "0.1.0", + "dependencies": { + "@kubernetes/client-node": "^1.0.0" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.0", + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + } + }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@kubernetes/client-node": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-1.4.0.tgz", + "integrity": "sha512-Zge3YvF7DJi264dU1b3wb/GmzR99JhUpqTvp+VGHfwZT+g7EOOYNScDJNZwXy9cszyIGPIs0VHr+kk8e95qqrA==", + "license": "Apache-2.0", + "dependencies": { + "@types/js-yaml": "^4.0.1", + "@types/node": "^24.0.0", + "@types/node-fetch": "^2.6.13", + "@types/stream-buffers": "^3.0.3", + "form-data": "^4.0.0", + "hpagent": "^1.2.0", + "isomorphic-ws": "^5.0.0", + "js-yaml": "^4.1.0", + "jsonpath-plus": "^10.3.0", + "node-fetch": "^2.7.0", + "openid-client": "^6.1.3", + "rfc4648": "^1.3.0", + "socks-proxy-agent": "^8.0.4", + "stream-buffers": "^3.0.2", + "tar-fs": "^3.0.9", + "ws": "^8.18.2" + } + }, + "node_modules/@kubernetes/client-node/node_modules/@types/node": { + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@kubernetes/client-node/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/@tsconfig/node22": { + "version": "22.0.5", + "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.5.tgz", + "integrity": "sha512-hLf2ld+sYN/BtOJjHUWOk568dvjFQkHnLNa6zce25GIH+vxKfvTgm3qpaH6ToF5tu/NN0IH66s+Bb5wElHrLcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/@types/stream-buffers": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.8.tgz", + "integrity": "sha512-J+7VaHKNvlNPJPEJXX/fKa9DZtR/xPMwuIbe+yNOwp1YB+ApUOBv2aUpEoBJEi8nJgbgs1x8e73ttg0r1rSUdw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/b4a": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", + "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.0.tgz", + "integrity": "sha512-xzqKsCFxAek9aezYhjJuJRXBIaYlg/0OGDTZp+T8eYmYMlm66cs6cYko02drIyjN2CBbi+I6L7YfXyqpqtKRXA==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz", + "integrity": "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.0.tgz", + "integrity": "sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA==", + "license": "Apache-2.0", + "dependencies": { + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz", + "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==", + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jose": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/jsonpath-plus": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.4.0.tgz", + "integrity": "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==", + "license": "MIT", + "dependencies": { + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/oauth4webapi": { + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.5.tgz", + "integrity": "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openid-client": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.2.tgz", + "integrity": "sha512-uOvTCndr4udZsKihJ68H9bUICrriHdUVJ6Az+4Ns6cW55rwM5h0bjVIzDz2SxgOI84LKjFyjOFvERLzdTUROGA==", + "license": "MIT", + "dependencies": { + "jose": "^6.1.3", + "oauth4webapi": "^3.8.4" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rfc4648": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.4.tgz", + "integrity": "sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==", + "license": "MIT" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/stream-buffers": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.3.tgz", + "integrity": "sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw==", + "license": "Unlicense", + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/streamx": { + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", + "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/tar-fs": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", + "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/deployment/opencode-cloudflare-operator/package.json b/deployment/opencode-cloudflare-operator/package.json new file mode 100644 index 000000000000..53755a7fd085 --- /dev/null +++ b/deployment/opencode-cloudflare-operator/package.json @@ -0,0 +1,21 @@ +{ + "name": "@opencode-ai/opencode-cloudflare-operator", + "version": "0.1.0", + "description": "Watches opencode session pods and manages Cloudflare DNS records + tunnel routes", + "type": "module", + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "typecheck": "tsc --noEmit", + "dev": "node --watch --env-file=.env.local dist/index.js", + "start": "node dist/index.js" + }, + "dependencies": { + "@kubernetes/client-node": "^1.0.0" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.0", + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + } +} diff --git a/deployment/opencode-cloudflare-operator/src/cloudflare.ts b/deployment/opencode-cloudflare-operator/src/cloudflare.ts new file mode 100644 index 000000000000..1638beb68d5e --- /dev/null +++ b/deployment/opencode-cloudflare-operator/src/cloudflare.ts @@ -0,0 +1,189 @@ +import { config } from "./config.js"; + +const CF_API = "https://api.cloudflare.com/client/v4"; + +async function cfFetch(path: string, method: string, body?: unknown): Promise { + const res = await fetch(`${CF_API}${path}`, { + method, + headers: { + Authorization: `Bearer ${config.cfApiToken}`, + "Content-Type": "application/json", + }, + body: body ? JSON.stringify(body) : undefined, + }); + + const data = (await res.json()) as { success: boolean; errors: { message: string }[]; result: unknown }; + + if (!data.success) { + const msgs = data.errors.map((e) => e.message).join(", "); + throw new Error(`Cloudflare API error [${method} ${path}]: ${msgs}`); + } + + return data.result; +} + +// --------------------------------------------------------------------------- +// DNS Records +// --------------------------------------------------------------------------- + +/** + * Find the Cloudflare DNS record ID for a given hostname, or null if not found. + */ +export async function findDnsRecord(hostname: string): Promise { + const result = (await cfFetch( + `/zones/${config.cfZoneId}/dns_records?name=${encodeURIComponent(hostname)}&type=CNAME`, + "GET" + )) as { id: string }[]; + + return result.length > 0 ? result[0].id : null; +} + +/** + * Create a proxied CNAME DNS record pointing to the Cloudflare tunnel. + * Idempotent — skips creation if the record already exists (checked via GET + * and also by treating the "already exists" POST error as a no-op, handling + * the race when two operator replicas try to create the same record). + */ +export async function createDnsRecord(hostname: string, tunnelCname: string): Promise { + const existing = await findDnsRecord(hostname); + if (existing) { + console.log(`DNS record already exists for ${hostname} (${existing}), skipping`); + return; + } + + try { + await cfFetch(`/zones/${config.cfZoneId}/dns_records`, "POST", { + type: "CNAME", + name: hostname, + content: tunnelCname, + proxied: true, + ttl: 1, + comment: "opencode session — managed by opencode-cloudflare-operator", + }); + } catch (err) { + // Two operator replicas may race — if the record was created between our + // GET and POST, treat "already exists" as a success. + if (err instanceof Error && err.message.includes("already exists")) { + console.log(`DNS record already exists for ${hostname} (created concurrently), skipping`); + return; + } + throw err; + } + + console.log(`Created DNS record: ${hostname} → ${tunnelCname}`); +} + +/** + * Delete the CNAME DNS record for a hostname. + * Idempotent — no-op if the record does not exist. + */ +export async function deleteDnsRecord(hostname: string): Promise { + const id = await findDnsRecord(hostname); + if (!id) { + console.log(`No DNS record found for ${hostname}, nothing to delete`); + return; + } + + await cfFetch(`/zones/${config.cfZoneId}/dns_records/${id}`, "DELETE"); + console.log(`Deleted DNS record: ${hostname}`); +} + +// --------------------------------------------------------------------------- +// Tunnel Routes +// --------------------------------------------------------------------------- + +/** + * Get the tunnel CNAME (e.g. .cfargotunnel.com) for the configured tunnel. + * The CNAME is always .cfargotunnel.com — the Cloudflare API does not + * expose a dedicated `cname` field; it is derived directly from the tunnel ID. + */ +export async function getTunnelCname(): Promise { + return `${config.cfTunnelId}.cfargotunnel.com`; +} + +/** Fetch the account ID from the zone (needed for tunnel API) */ +let _accountId: string | null = null; +async function getAccountId(): Promise { + if (_accountId) return _accountId; + + const result = (await cfFetch(`/zones/${config.cfZoneId}`, "GET")) as { + account: { id: string }; + }; + + _accountId = result.account.id; + return _accountId; +} + +/** + * Get the current tunnel ingress configuration. + */ +async function getTunnelConfig(): Promise<{ ingress: IngressRule[] }> { + const result = (await cfFetch( + `/accounts/${await getAccountId()}/cfd_tunnel/${config.cfTunnelId}/configurations`, + "GET" + )) as { config: { ingress: IngressRule[] } }; + + return result.config; +} + +interface IngressRule { + hostname?: string; + service: string; +} + +/** + * Add a tunnel ingress rule for the given hostname → router service. + * Idempotent — skips if already present. + * Keeps the catch-all rule (no hostname) at the end. + */ +export async function createTunnelRoute(hostname: string): Promise { + const current = await getTunnelConfig(); + const ingress = current.ingress ?? []; + + // Check if already exists + if (ingress.some((r) => r.hostname === hostname)) { + console.log(`Tunnel route already exists for ${hostname}, skipping`); + return; + } + + // Insert before the catch-all (last entry has no hostname) + const catchAll = ingress.filter((r) => !r.hostname); + const named = ingress.filter((r) => r.hostname && r.hostname !== hostname); + + const newIngress: IngressRule[] = [ + ...named, + { hostname, service: config.routerServiceUrl }, + ...catchAll, + ]; + + await cfFetch( + `/accounts/${await getAccountId()}/cfd_tunnel/${config.cfTunnelId}/configurations`, + "PUT", + { config: { ingress: newIngress } } + ); + + console.log(`Created tunnel route: ${hostname} → ${config.routerServiceUrl}`); +} + +/** + * Remove the tunnel ingress rule for the given hostname. + * Idempotent — no-op if not present. + */ +export async function deleteTunnelRoute(hostname: string): Promise { + const current = await getTunnelConfig(); + const ingress = current.ingress ?? []; + + const filtered = ingress.filter((r) => r.hostname !== hostname); + if (filtered.length === ingress.length) { + console.log(`No tunnel route found for ${hostname}, nothing to delete`); + return; + } + + await cfFetch( + `/accounts/${await getAccountId()}/cfd_tunnel/${config.cfTunnelId}/configurations`, + "PUT", + { config: { ingress: filtered } } + ); + + console.log(`Deleted tunnel route: ${hostname}`); +} diff --git a/deployment/opencode-cloudflare-operator/src/config.ts b/deployment/opencode-cloudflare-operator/src/config.ts new file mode 100644 index 000000000000..fc309ac3afdf --- /dev/null +++ b/deployment/opencode-cloudflare-operator/src/config.ts @@ -0,0 +1,52 @@ +function required(name: string): string { + const value = process.env[name]; + if (!value) throw new Error(`Missing required environment variable: ${name}`); + return value; +} + +export const config = { + /** Kubernetes namespace to watch for session pods */ + watchNamespace: process.env.WATCH_NAMESPACE ?? "opencode-router", + /** Label selector identifying session pods */ + podLabelSelector: + process.env.POD_LABEL_SELECTOR ?? "app.kubernetes.io/managed-by=opencode-router", + /** Label key holding the 12-char hex session hash */ + sessionHashLabel: "opencode.ai/session-hash", + /** Cloudflare API token (DNS:Edit + Zone:Read) */ + cfApiToken: required("CF_API_TOKEN"), + /** Cloudflare Zone ID */ + cfZoneId: required("CF_ZONE_ID"), + /** Cloudflare Tunnel ID */ + cfTunnelId: required("CF_TUNNEL_ID"), + /** Base domain, e.g. "no-panic.org" */ + domain: required("DOMAIN"), + /** Suffix appended to the hash, e.g. "-oc" → -oc. */ + routeSuffix: process.env.ROUTE_SUFFIX ?? "", + /** In-cluster router service URL all session traffic is forwarded to */ + routerServiceUrl: required("ROUTER_SERVICE_URL"), + /** Port for the health check HTTP server */ + healthPort: Number(process.env.HEALTH_PORT ?? 8080), + /** + * Namespace where IngressRoute resources are created. + * Must match the namespace that owns the oauth2 chain middleware. + * Defaults to watchNamespace (opencode-router). + */ + ingressRouteNamespace: process.env.INGRESSROUTE_NAMESPACE ?? process.env.WATCH_NAMESPACE ?? "opencode-router", + /** + * Name of the Traefik Middleware chain that enforces OAuth2 auth. + * Created by ExposedWebApp for the main router domain; reused for session routes. + * e.g. "opencode-router-oauth2-chain" + */ + oauth2ChainMiddleware: process.env.OAUTH2_CHAIN_MIDDLEWARE ?? "opencode-router-oauth2-chain", + /** + * Name of the in-cluster Kubernetes Service for the opencode-router. + * Session IngressRoutes forward all traffic here; the router then proxies + * to the correct pod based on the hash in the hostname. + */ + routerServiceName: process.env.ROUTER_SERVICE_NAME ?? "opencode-router", +}; + +/** Compute the public hostname for a given session hash */ +export function sessionHostname(hash: string): string { + return `${hash}${config.routeSuffix}.${config.domain}`; +} diff --git a/deployment/opencode-cloudflare-operator/src/index.ts b/deployment/opencode-cloudflare-operator/src/index.ts new file mode 100644 index 000000000000..aef32f9f4554 --- /dev/null +++ b/deployment/opencode-cloudflare-operator/src/index.ts @@ -0,0 +1,165 @@ +import http from "node:http"; +import * as k8s from "@kubernetes/client-node"; +import { config, sessionHostname } from "./config.js"; +import { + createDnsRecord, + createTunnelRoute, + deleteDnsRecord, + deleteTunnelRoute, + getTunnelCname, +} from "./cloudflare.js"; +import { createIngressRoutes, deleteIngressRoutes } from "./ingressroute.js"; + +// --------------------------------------------------------------------------- +// Kubernetes client +// --------------------------------------------------------------------------- + +const kc = new k8s.KubeConfig(); +import fs from "node:fs"; +if (fs.existsSync("/var/run/secrets/kubernetes.io/serviceaccount/token")) { + kc.loadFromCluster(); +} else { + kc.loadFromDefault(); +} + +// --------------------------------------------------------------------------- +// Pod watch +// --------------------------------------------------------------------------- + +/** + * Provision Cloudflare DNS record + tunnel route for a new session pod. + */ +async function onPodAdded(pod: k8s.V1Pod): Promise { + const hash = pod.metadata?.labels?.[config.sessionHashLabel]; + if (!hash) return; + + const hostname = sessionHostname(hash); + console.log(`Pod added: ${pod.metadata?.name} → provisioning ${hostname}`); + + try { + const tunnelCname = await getTunnelCname(); + // Create DNS record, tunnel route, and Traefik IngressRoutes in parallel + await Promise.all([ + createDnsRecord(hostname, tunnelCname), + createTunnelRoute(hostname), + createIngressRoutes(hostname), + ]); + console.log(`Provisioned ${hostname}`); + } catch (err) { + console.error(`Failed to provision ${hostname}:`, err); + } +} + +/** + * Remove Cloudflare DNS record + tunnel route when a session pod is deleted. + */ +async function onPodDeleted(pod: k8s.V1Pod): Promise { + const hash = pod.metadata?.labels?.[config.sessionHashLabel]; + if (!hash) return; + + const hostname = sessionHostname(hash); + console.log(`Pod deleted: ${pod.metadata?.name} → deprovisioning ${hostname}`); + + try { + await Promise.all([ + deleteDnsRecord(hostname), + deleteTunnelRoute(hostname), + deleteIngressRoutes(hostname), + ]); + console.log(`Deprovisioned ${hostname}`); + } catch (err) { + console.error(`Failed to deprovision ${hostname}:`, err); + } +} + +/** + * Start watching pods with exponential backoff on failure. + */ +async function startWatch(): Promise { + const watch = new k8s.Watch(kc); + let backoffMs = 1000; + + const doWatch = async (): Promise => { + console.log( + `Watching pods in namespace "${config.watchNamespace}" ` + + `with selector "${config.podLabelSelector}"` + ); + + try { + await watch.watch( + `/api/v1/namespaces/${config.watchNamespace}/pods`, + { labelSelector: config.podLabelSelector }, + (eventType: string, pod: k8s.V1Pod) => { + if (eventType === "ADDED") { + void onPodAdded(pod); + } else if (eventType === "DELETED") { + void onPodDeleted(pod); + } + // MODIFIED: ignore — we don't need to react to pod updates + }, + (err: unknown) => { + if (err) { + console.error("Watch stream error:", err); + } else { + console.log("Watch stream ended — restarting"); + } + backoffMs = Math.min(backoffMs * 2, 30_000); + setTimeout(() => void doWatch(), backoffMs); + } + ); + // Reset backoff on successful connection + backoffMs = 1000; + } catch (err) { + console.error("Watch setup error:", err); + setTimeout(() => void doWatch(), backoffMs); + backoffMs = Math.min(backoffMs * 2, 30_000); + } + }; + + await doWatch(); +} + +// --------------------------------------------------------------------------- +// Health check server +// --------------------------------------------------------------------------- + +const healthServer = http.createServer((req, res) => { + if (req.url === "/healthz") { + res.writeHead(200, { "Content-Type": "text/plain" }).end("ok"); + } else { + res.writeHead(404).end(); + } +}); + +healthServer.listen(config.healthPort, () => { + console.log(`Health server listening on :${config.healthPort}`); +}); + +// --------------------------------------------------------------------------- +// Startup +// --------------------------------------------------------------------------- + +console.log("opencode-cloudflare-operator starting"); +console.log(` Domain : ${config.domain}`); +console.log(` Route suffix : "${config.routeSuffix}"`); +console.log(` Session URL : ${config.routeSuffix}.${config.domain}`); +console.log(` Router svc : ${config.routerServiceUrl}`); +console.log(` Tunnel ID : ${config.cfTunnelId}`); +console.log(` IngressRoute ns : ${config.ingressRouteNamespace}`); +console.log(` OAuth2 middleware : ${config.oauth2ChainMiddleware}`); +console.log(` Router svc name : ${config.routerServiceName}`); + +void startWatch(); + +// --------------------------------------------------------------------------- +// Graceful shutdown +// --------------------------------------------------------------------------- + +function shutdown(): void { + console.log("Shutting down..."); + healthServer.close(() => process.exit(0)); + setTimeout(() => process.exit(1), 5_000); +} + +process.on("SIGTERM", shutdown); +process.on("SIGINT", shutdown); diff --git a/deployment/opencode-cloudflare-operator/src/ingressroute.ts b/deployment/opencode-cloudflare-operator/src/ingressroute.ts new file mode 100644 index 000000000000..ad69fecd16cc --- /dev/null +++ b/deployment/opencode-cloudflare-operator/src/ingressroute.ts @@ -0,0 +1,198 @@ +import * as k8s from "@kubernetes/client-node"; +import { config } from "./config.js"; + +/** + * Traefik IngressRoute CRD spec shapes (minimal, untyped — we POST raw JSON). + */ +interface IngressRouteSpec { + entryPoints: string[]; + routes: { + match: string; + kind: "Rule"; + middlewares?: { name: string; namespace: string }[]; + services: { name: string; namespace: string; port: number }[]; + priority?: number; + }[]; +} + +// --------------------------------------------------------------------------- +// Kubernetes custom objects client +// --------------------------------------------------------------------------- + +const kc = new k8s.KubeConfig(); +import fs from "node:fs"; +if (fs.existsSync("/var/run/secrets/kubernetes.io/serviceaccount/token")) { + kc.loadFromCluster(); +} else { + kc.loadFromDefault(); +} + +const customObjectsApi = kc.makeApiClient(k8s.CustomObjectsApi); + +const TRAEFIK_GROUP = "traefik.io"; +const TRAEFIK_VERSION = "v1alpha1"; +const INGRESSROUTE_PLURAL = "ingressroutes"; + +/** Derive stable IngressRoute names from the session hash */ +function appRouteName(hash: string): string { + return `opencode-session-${hash}-app`; +} +function signinRouteName(hash: string): string { + return `opencode-session-${hash}-signin`; +} + +/** + * Create the two IngressRoute resources for a session hostname: + * 1. Signin route: Host(``) && PathPrefix(`/oauth2/`) → oauth2-proxy + * 2. App route: Host(``) → opencode-router service + * + * Both mirror exactly what ExposedWebApp creates for the main opencode-router domain, + * but point at the per-session hostname instead. Traffic hits Traefik via the + * Cloudflare Tunnel and is routed to the opencode-router service (which then proxies + * to the correct session pod via subdomain-based hash extraction). + * + * Idempotent — skips creation if the route already exists (HTTP 409). + */ +export async function createIngressRoutes(hostname: string): Promise { + const ns = config.ingressRouteNamespace; + + // Route 1 — /oauth2/* → oauth2-proxy-users (signin flow, no middleware) + const signinSpec: IngressRouteSpec = { + entryPoints: ["web"], + routes: [ + { + match: `Host(\`${hostname}\`) && PathPrefix(\`/oauth2/\`)`, + kind: "Rule", + services: [ + { + name: "oauth2-proxy-users", + namespace: "oauth2-proxy", + port: 80, + }, + ], + }, + ], + }; + + // Route 2 — /* → opencode-router service, protected by oauth2 chain middleware + const appSpec: IngressRouteSpec = { + entryPoints: ["web"], + routes: [ + { + match: `Host(\`${hostname}\`)`, + kind: "Rule", + middlewares: [ + { + name: config.oauth2ChainMiddleware, + namespace: ns, + }, + ], + services: [ + { + name: config.routerServiceName, + namespace: ns, + port: 80, + }, + ], + priority: 1, + }, + ], + }; + + await Promise.all([ + createOrSkip(signinRouteName(hostname.split(".")[0]), ns, signinSpec), + createOrSkip(appRouteName(hostname.split(".")[0]), ns, appSpec), + ]); + + console.log(`Created IngressRoutes for ${hostname}`); +} + +/** + * Delete the two IngressRoute resources for a session hostname. + * Idempotent — no-op if either resource is already gone. + */ +export async function deleteIngressRoutes(hostname: string): Promise { + const ns = config.ingressRouteNamespace; + const hash = hostname.split(".")[0]; // e.g. "8b6cc2aa9a45-oc" — used only for name derivation + + await Promise.all([ + deleteOrSkip(signinRouteName(hash), ns), + deleteOrSkip(appRouteName(hash), ns), + ]); + + console.log(`Deleted IngressRoutes for ${hostname}`); +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +async function createOrSkip( + name: string, + namespace: string, + spec: IngressRouteSpec +): Promise { + try { + await customObjectsApi.createNamespacedCustomObject({ + group: TRAEFIK_GROUP, + version: TRAEFIK_VERSION, + namespace, + plural: INGRESSROUTE_PLURAL, + body: { + apiVersion: `${TRAEFIK_GROUP}/${TRAEFIK_VERSION}`, + kind: "IngressRoute", + metadata: { + name, + namespace, + labels: { "app.kubernetes.io/managed-by": "opencode-cloudflare-operator" }, + }, + spec, + }, + }); + console.log(`Created IngressRoute ${namespace}/${name}`); + } catch (err: unknown) { + if (isConflict(err)) { + console.log(`IngressRoute ${namespace}/${name} already exists, skipping`); + return; + } + throw err; + } +} + +async function deleteOrSkip(name: string, namespace: string): Promise { + try { + await customObjectsApi.deleteNamespacedCustomObject({ + group: TRAEFIK_GROUP, + version: TRAEFIK_VERSION, + namespace, + plural: INGRESSROUTE_PLURAL, + name, + }); + console.log(`Deleted IngressRoute ${namespace}/${name}`); + } catch (err: unknown) { + if (isNotFound(err)) { + console.log(`IngressRoute ${namespace}/${name} not found, skipping`); + return; + } + throw err; + } +} + +function hasStatus(err: unknown): err is { response: { statusCode: number } } { + return ( + typeof err === "object" && + err !== null && + "response" in err && + typeof (err as { response: unknown }).response === "object" && + (err as { response: { statusCode?: unknown } }).response !== null && + "statusCode" in (err as { response: { statusCode?: unknown } }).response + ); +} + +function isNotFound(err: unknown): boolean { + return hasStatus(err) && err.response.statusCode === 404; +} + +function isConflict(err: unknown): boolean { + return hasStatus(err) && err.response.statusCode === 409; +} diff --git a/deployment/opencode-cloudflare-operator/tsconfig.json b/deployment/opencode-cloudflare-operator/tsconfig.json new file mode 100644 index 000000000000..2cd5190fccd4 --- /dev/null +++ b/deployment/opencode-cloudflare-operator/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "declaration": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/opencode-router/scripts/build-image.sh b/packages/opencode-router/scripts/build-image.sh new file mode 100755 index 000000000000..46d7249a09d8 --- /dev/null +++ b/packages/opencode-router/scripts/build-image.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# build.sh — Build and push the opencode-router image to GHCR. +# +# Usage: +# ./images/opencode-router/build.sh [--push] [--revision ] [--ghcr-user ] [--token ] +# +# The image tag is: ghcr.io//opencode-router:-homelab. +# e.g. ghcr.io/mrsimpson/opencode-router:0.0.1-homelab.1 +# +# The build context is the opencode monorepo root (~/projects/open-source/opencode). +# The Dockerfile lives at packages/opencode-router/Dockerfile within that monorepo. +# +# Options: +# --push Push the image after building (default: build only) +# --revision Custom revision suffix (default: 1) +# --ghcr-user GitHub username / org for GHCR (default: mrsimpson) +# --token GitHub PAT with write:packages scope (bypasses keychain) +# Can also be set via GITHUB_PAT env var +# --monorepo Path to the opencode monorepo root +# (default: ~/projects/open-source/opencode) +# +# Examples: +# ./images/opencode-router/build.sh # build only +# ./images/opencode-router/build.sh --push --token ghp_xxx # build + push +# ./images/opencode-router/build.sh --push --revision 2 # bump homelab revision +# GITHUB_PAT=ghp_xxx ./images/opencode-router/build.sh --push # via env var + +set -euo pipefail + +GHCR_USER="mrsimpson" +IMAGE_NAME="opencode-router" + +PUSH=false +REVISION=1 +TOKEN="${GITHUB_PAT:-}" +MONOREPO="${HOME}/projects/open-source/opencode" + +# --------------------------------------------------------------------------- +# Parse arguments +# --------------------------------------------------------------------------- +while [[ $# -gt 0 ]]; do + case "$1" in + --push) PUSH=true; shift ;; + --revision) REVISION="$2"; shift 2 ;; + --ghcr-user) GHCR_USER="$2"; shift 2 ;; + --token) TOKEN="$2"; shift 2 ;; + --monorepo) MONOREPO="$2"; shift 2 ;; + *) echo "Unknown option: $1" >&2; exit 1 ;; + esac +done + +# --------------------------------------------------------------------------- +# Resolve package version from monorepo +# --------------------------------------------------------------------------- +PACKAGE_JSON="${MONOREPO}/packages/opencode-router/package.json" + +if [[ ! -f "${PACKAGE_JSON}" ]]; then + echo "✗ Cannot find ${PACKAGE_JSON}" >&2 + echo " Is the monorepo at ${MONOREPO}? Use --monorepo to override." >&2 + exit 1 +fi + +PACKAGE_VERSION=$(node -p "require('${PACKAGE_JSON}').version" 2>/dev/null) + +if [[ -z "${PACKAGE_VERSION}" ]]; then + echo "✗ Could not read version from ${PACKAGE_JSON}" >&2 + exit 1 +fi + +echo "→ opencode-router package version: ${PACKAGE_VERSION}" +echo " Revision : ${REVISION}" + +TAG="${PACKAGE_VERSION}-homelab.${REVISION}" +REGISTRY="ghcr.io/${GHCR_USER}" +FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}" +LATEST_IMAGE="${REGISTRY}/${IMAGE_NAME}:latest" +DOCKERFILE="${MONOREPO}/packages/opencode-router/Dockerfile" + +echo " Final image tag : ${FULL_IMAGE}" +echo " Build context : ${MONOREPO}" +echo " Dockerfile : ${DOCKERFILE}" +echo "" + +# --------------------------------------------------------------------------- +# Build +# --------------------------------------------------------------------------- +echo "→ Building ${FULL_IMAGE} (linux/amd64) ..." +# Always target linux/amd64 — the homelab cluster runs on x86_64. +# Build context is the monorepo root so the Dockerfile can access other packages. +docker buildx build \ + --platform linux/amd64 \ + --file "${DOCKERFILE}" \ + --label "org.opencontainers.image.version=${TAG}" \ + --label "org.opencontainers.image.source=https://github.com/mrsimpson/homelab" \ + --label "org.opencontainers.image.description=opencode-router: per-user pod router for opencode" \ + --tag "${FULL_IMAGE}" \ + --tag "${LATEST_IMAGE}" \ + --load \ + "${MONOREPO}" + +echo "✓ Build complete: ${FULL_IMAGE}" +echo "" + +# --------------------------------------------------------------------------- +# Push (optional) +# --------------------------------------------------------------------------- +if [[ "${PUSH}" == "true" ]]; then + # If a token was provided, log in explicitly (bypasses keychain) + if [[ -n "${TOKEN}" ]]; then + echo "→ Logging in to ghcr.io as ${GHCR_USER} ..." + echo "${TOKEN}" | docker login ghcr.io -u "${GHCR_USER}" --password-stdin + fi + + echo "→ Pushing ${FULL_IMAGE} ..." + # Re-run buildx with --push to push the linux/amd64 manifest directly to the registry + docker buildx build \ + --platform linux/amd64 \ + --file "${DOCKERFILE}" \ + --label "org.opencontainers.image.version=${TAG}" \ + --label "org.opencontainers.image.source=https://github.com/mrsimpson/homelab" \ + --label "org.opencontainers.image.description=opencode-router: per-user pod router for opencode" \ + --tag "${FULL_IMAGE}" \ + --tag "${LATEST_IMAGE}" \ + --push \ + "${MONOREPO}" + echo "✓ Pushed: ${FULL_IMAGE}" + echo "✓ Pushed: ${LATEST_IMAGE}" + echo "" + echo "Update the Pulumi config to:" + echo " pulumi config set opencode:routerImage \"${FULL_IMAGE}\"" +else + echo "ℹ Skipping push (pass --push to push to ${REGISTRY})" + echo "" + echo "When ready, run:" + echo " ${BASH_SOURCE[0]} --push --token \$GITHUB_PAT" +fi From cf63feea8bdbacbdb653bd3a0575a3cfa8002f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Mon, 13 Apr 2026 09:33:22 +0200 Subject: [PATCH 003/616] =?UTF-8?q?feat(router):=20session=20lifecycle=20?= =?UTF-8?q?=E2=80=94=20PVC=20identity,=20stopped=20state,=20terminate/resu?= =?UTF-8?q?me/suggest-branch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PVC is now the source of truth for session identity; listUserSessions scans PVCs and correlates with pod state to produce creating/running/stopped - terminateSession: deletes pod + PVC (destructive, unrecoverable) - resumeSession: recreates pod for a stopped session (idempotent) - suggestBranch: collision-safe human-id branch name generation - sessionBranch vs sourceBranch split: user enters sourceBranch to start from, sessionBranch is auto-generated and forms the PVC/hash identity - GET /api/sessions/:hash and list now include lastActivity + idleTimeoutMinutes - Idle timeout default reduced from 30 → 15 minutes - dev-proxy: auto kubectl port-forward per session for local development - git-init resume fix: checkout existing branch instead of failing with -b --- bun.lock | 8 +- packages/opencode-router/.env.local.example | 2 +- packages/opencode-router/package.json | 6 +- .../scripts/create-local-kubeconfig.sh | 4 +- packages/opencode-router/src/api.ts | 163 +++++--- packages/opencode-router/src/config.ts | 10 +- packages/opencode-router/src/dev-proxy.ts | 115 ++++++ packages/opencode-router/src/index.ts | 153 ++++---- packages/opencode-router/src/pod-manager.ts | 350 ++++++++++++------ 9 files changed, 565 insertions(+), 246 deletions(-) create mode 100644 packages/opencode-router/src/dev-proxy.ts diff --git a/bun.lock b/bun.lock index 044d9aa17151..5a9116748e31 100644 --- a/bun.lock +++ b/bun.lock @@ -431,6 +431,7 @@ "dependencies": { "@kubernetes/client-node": "^1.0.0", "http-proxy": "^1.18.1", + "human-id": "4.1.3", }, "devDependencies": { "@tsconfig/node22": "^22.0.0", @@ -450,6 +451,7 @@ "devDependencies": { "@tailwindcss/vite": "catalog:", "@tsconfig/node22": "catalog:", + "bun-types": "1.3.12", "tailwindcss": "catalog:", "typescript": "catalog:", "vite": "catalog:", @@ -2582,7 +2584,7 @@ "bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="], - "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + "bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], "bun-webgpu": ["bun-webgpu@0.1.5", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.5", "bun-webgpu-darwin-x64": "^0.1.5", "bun-webgpu-linux-x64": "^0.1.5", "bun-webgpu-win32-x64": "^0.1.5" } }, "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA=="], @@ -3308,6 +3310,8 @@ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "human-id": ["human-id@4.1.3", "", { "bin": { "human-id": "dist/cli.js" } }, "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q=="], + "human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="], "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], @@ -5500,6 +5504,8 @@ "@tufjs/models/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + "@types/bun/bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + "@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], "@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="], diff --git a/packages/opencode-router/.env.local.example b/packages/opencode-router/.env.local.example index 8930ed205c7c..1ab0681719a8 100644 --- a/packages/opencode-router/.env.local.example +++ b/packages/opencode-router/.env.local.example @@ -10,7 +10,7 @@ DEV_EMAIL=dev@local.test # --- Kubernetes --- KUBECONFIG=/tmp/opencode-router-local.kubeconfig -OPENCODE_NAMESPACE=opencode-router +OPENCODE_NAMESPACE=code # Dev-only: the homelab k3s cluster uses a self-signed cert. Bun's TLS stack does not # pick up the certificate-authority-data from the kubeconfig the way kubectl does. # This disables TLS verification for the k8s API calls — safe for local dev only. diff --git a/packages/opencode-router/package.json b/packages/opencode-router/package.json index 68c43c6d919b..6ebe1b49869d 100644 --- a/packages/opencode-router/package.json +++ b/packages/opencode-router/package.json @@ -7,11 +7,13 @@ "build": "tsc", "typecheck": "tsc --noEmit", "start": "node dist/index.js", - "dev": "bun --env-file=.env.local --watch src/index.ts" + "dev": "bun --env-file=.env.local --watch src/index.ts", + "test": "bun test" }, "dependencies": { "@kubernetes/client-node": "^1.0.0", - "http-proxy": "^1.18.1" + "http-proxy": "^1.18.1", + "human-id": "4.1.3" }, "devDependencies": { "@tsconfig/node22": "^22.0.0", diff --git a/packages/opencode-router/scripts/create-local-kubeconfig.sh b/packages/opencode-router/scripts/create-local-kubeconfig.sh index cfa163524172..7d996132610a 100755 --- a/packages/opencode-router/scripts/create-local-kubeconfig.sh +++ b/packages/opencode-router/scripts/create-local-kubeconfig.sh @@ -12,8 +12,8 @@ set -euo pipefail -NAMESPACE="${OPENCODE_NAMESPACE:-opencode-router}" -SA_NAME="opencode-router" +NAMESPACE="${OPENCODE_NAMESPACE:-code}" +SA_NAME="${SA_NAME:-code}" KUBECONFIG_OUT="${KUBECONFIG_OUT:-/tmp/opencode-router-local.kubeconfig}" TOKEN_DURATION="${TOKEN_DURATION:-24h}" diff --git a/packages/opencode-router/src/api.ts b/packages/opencode-router/src/api.ts index 90bc9d32b874..881ca9c5fde2 100644 --- a/packages/opencode-router/src/api.ts +++ b/packages/opencode-router/src/api.ts @@ -1,5 +1,5 @@ -import type http from "node:http"; -import { config } from "./config.js"; +import type http from "node:http" +import { config } from "./config.js" import { type SessionKey, ensurePVC, @@ -7,23 +7,31 @@ import { getPodState, getSessionHash, listUserSessions, -} from "./pod-manager.js"; + terminateSession, + resumeSession, + suggestBranch, +} from "./pod-manager.js" function json(res: http.ServerResponse, status: number, body: unknown): void { - res.writeHead(status, { "Content-Type": "application/json" }).end(JSON.stringify(body)); + res.writeHead(status, { "Content-Type": "application/json" }).end(JSON.stringify(body)) +} + +function errorCode(err: unknown): string { + if (err instanceof Error) return err.message + return "Unknown" } async function readBody(req: http.IncomingMessage): Promise { - const chunks: Buffer[] = []; - for await (const chunk of req) chunks.push(chunk as Buffer); - return Buffer.concat(chunks).toString("utf-8"); + const chunks: Buffer[] = [] + for await (const chunk of req) chunks.push(chunk as Buffer) + return Buffer.concat(chunks).toString("utf-8") } /** Build the public URL for a session subdomain. */ function sessionUrl(hash: string, req: http.IncomingMessage): string { // Derive scheme from X-Forwarded-Proto (set by Traefik) or default to http in dev - const proto = (req.headers["x-forwarded-proto"] as string | undefined) ?? "http"; - return `${proto}://${hash}${config.routeSuffix}.${config.routerDomain}`; + const proto = (req.headers["x-forwarded-proto"] as string | undefined) ?? "http" + return `${proto}://${hash}${config.routeSuffix}.${config.routerDomain}` } /** @@ -34,61 +42,132 @@ function sessionUrl(hash: string, req: http.IncomingMessage): string { * POST /api/sessions → create a session {repoUrl, branch} → {hash, url, state} * GET /api/sessions/:hash → get state of a specific session */ -export async function handleApi( - req: http.IncomingMessage, - res: http.ServerResponse, - email: string -): Promise { - const url = req.url ?? "/"; +export async function handleApi(req: http.IncomingMessage, res: http.ServerResponse, email: string): Promise { + const url = req.url ?? "/" // GET /api/sessions — list all sessions for this user, always includes email if (url === "/api/sessions" && req.method === "GET") { - const sessions = await listUserSessions(email, req); - json(res, 200, { email, sessions }); - return true; + const sessions = await listUserSessions(email, req) + json(res, 200, { email, sessions }) + return true } // POST /api/sessions — create a new session if (url === "/api/sessions" && req.method === "POST") { - const raw = await readBody(req); - let repoUrl: string; - let branch: string; + const raw = await readBody(req) + let repoUrl: string + let branch: string + let sourceBranch: string try { - const body = JSON.parse(raw); - repoUrl = typeof body.repoUrl === "string" ? body.repoUrl.trim() : ""; - branch = typeof body.branch === "string" ? body.branch.trim() : ""; + const body = JSON.parse(raw) + repoUrl = typeof body.repoUrl === "string" ? body.repoUrl.trim() : "" + branch = typeof body.branch === "string" ? body.branch.trim() : "" + sourceBranch = typeof body.sourceBranch === "string" ? body.sourceBranch.trim() : "" } catch { - json(res, 400, { error: "Invalid JSON" }); - return true; + json(res, 400, { error: "Invalid JSON" }) + return true } if (!repoUrl) { - json(res, 400, { error: "repoUrl is required" }); - return true; + json(res, 400, { error: "repoUrl is required" }) + return true } if (!branch) { - json(res, 400, { error: "branch is required" }); - return true; + json(res, 400, { error: "branch is required" }) + return true } + if (!sourceBranch) { + json(res, 400, { error: "sourceBranch is required" }) + return true + } + + const session: SessionKey = { email, repoUrl, branch, sourceBranch } + const hash = getSessionHash(email, repoUrl, branch) - const session: SessionKey = { email, repoUrl, branch }; - const hash = getSessionHash(email, repoUrl, branch); + await ensurePVC(session) + await ensurePod(session) - await ensurePVC(session); - await ensurePod(session); + json(res, 201, { hash, url: sessionUrl(hash, req), state: "creating" }) + return true + } - json(res, 201, { hash, url: sessionUrl(hash, req), state: "creating" }); - return true; + // GET /api/sessions/suggest-branch — must be before /:hash regex + if (url.startsWith("/api/sessions/suggest-branch") && req.method === "GET") { + const repoUrl = new URL(url, "http://localhost").searchParams.get("repoUrl") + if (!repoUrl) { + json(res, 400, { error: "repoUrl is required" }) + return true + } + const branch = await suggestBranch(email, repoUrl) + json(res, 200, { branch }) + return true } // GET /api/sessions/:hash — get state of a specific session - const sessionMatch = url.match(/^\/api\/sessions\/([a-f0-9]{12})$/); + const sessionMatch = url.match(/^\/api\/sessions\/([a-f0-9]{12})$/) if (sessionMatch && req.method === "GET") { - const hash = sessionMatch[1]; - const state = await getPodState(hash); - json(res, 200, { hash, state, url: sessionUrl(hash, req) }); - return true; + const hash = sessionMatch[1] + const sessions = await listUserSessions(email, req) + const session = sessions.find((s) => s.hash === hash) + if (session) { + json(res, 200, session) + return true + } + // Fallback: not owned by this user but hash exists + const state = await getPodState(hash) + json(res, 200, { + hash, + state, + url: sessionUrl(hash, req), + lastActivity: new Date().toISOString(), + idleTimeoutMinutes: config.idleTimeoutMinutes, + }) + return true + } + + // POST /api/sessions/:hash/resume + const resumeMatch = url.match(/^\/api\/sessions\/([a-f0-9]{12})\/resume$/) + if (resumeMatch && req.method === "POST") { + const hash = resumeMatch[1] + try { + await resumeSession(hash, email) + } catch (err) { + const code = errorCode(err) + if (code === "Forbidden") { + json(res, 403, { error: "Forbidden" }) + return true + } + if (code === "NotFound") { + json(res, 404, { error: "Not found" }) + return true + } + throw err + } + json(res, 200, { hash, state: "creating", url: sessionUrl(hash, req) }) + return true + } + + // DELETE /api/sessions/:hash + const deleteMatch = url.match(/^\/api\/sessions\/([a-f0-9]{12})$/) + if (deleteMatch && req.method === "DELETE") { + const hash = deleteMatch[1] + try { + await terminateSession(hash, email) + } catch (err) { + const code = errorCode(err) + if (code === "Forbidden") { + json(res, 403, { error: "Forbidden" }) + return true + } + if (code === "NotFound") { + json(res, 404, { error: "Not found" }) + return true + } + throw err + } + json(res, 204, null) + return true } - return false; + return false } diff --git a/packages/opencode-router/src/config.ts b/packages/opencode-router/src/config.ts index 65529022fdda..153dd054b412 100644 --- a/packages/opencode-router/src/config.ts +++ b/packages/opencode-router/src/config.ts @@ -1,7 +1,7 @@ function required(name: string): string { - const value = process.env[name]; - if (!value) throw new Error(`Missing required environment variable: ${name}`); - return value; + const value = process.env[name] + if (!value) throw new Error(`Missing required environment variable: ${name}`) + return value } export const config = { @@ -9,7 +9,7 @@ export const config = { namespace: process.env.OPENCODE_NAMESPACE ?? "opencode", opencodeImage: required("OPENCODE_IMAGE"), opencodePort: 4096, - idleTimeoutMinutes: Number(process.env.IDLE_TIMEOUT_MINUTES ?? 30), + idleTimeoutMinutes: Number(process.env.IDLE_TIMEOUT_MINUTES ?? 15), apiKeySecretName: process.env.API_KEY_SECRET_NAME ?? "opencode-api-keys", configMapName: process.env.CONFIG_MAP_NAME ?? "opencode-config-dir", imagePullSecretName: process.env.IMAGE_PULL_SECRET_NAME ?? "", @@ -47,4 +47,4 @@ export const config = { * Set this and port-forward the user pod: kubectl port-forward 4096:4096 -n opencode-router */ devPodProxyTarget: process.env.DEV_POD_PROXY_TARGET, -} as const; +} as const diff --git a/packages/opencode-router/src/dev-proxy.ts b/packages/opencode-router/src/dev-proxy.ts new file mode 100644 index 000000000000..036a32680984 --- /dev/null +++ b/packages/opencode-router/src/dev-proxy.ts @@ -0,0 +1,115 @@ +import { spawn, type ChildProcess } from "node:child_process" +import net from "node:net" +import { config } from "./config.js" + +/** + * Dev-mode helper: manages kubectl port-forward processes per session. + * In production (in-cluster), pod IPs are directly routable. + * Locally, we need kubectl port-forward to bridge the gap. + */ + +interface Forward { + port: number + proc: ChildProcess + ready: Promise +} + +const forwards = new Map() + +/** True when running outside the cluster (dev mode). */ +export const enabled = !!(config.devEmail || config.devViteUrl || config.devPodProxyTarget) + +async function freePort(): Promise { + return new Promise((resolve, reject) => { + const srv = net.createServer() + srv.listen(0, () => { + const addr = srv.address() + if (!addr || typeof addr === "string") { + srv.close(() => reject(new Error("failed to get port"))) + return + } + const port = addr.port + srv.close(() => resolve(port)) + }) + srv.on("error", reject) + }) +} + +/** + * Get a local proxy target URL for a session pod. + * Spawns kubectl port-forward on first call per hash; reuses on subsequent calls. + * Returns `http://localhost:` or null if the forward fails. + */ +export async function target(hash: string): Promise { + // Single fixed target overrides everything + if (config.devPodProxyTarget) return config.devPodProxyTarget + + const existing = forwards.get(hash) + if (existing) { + try { + const port = await existing.ready + return `http://localhost:${port}` + } catch { + // Previous forward failed; clean up and retry below + forwards.delete(hash) + } + } + + const port = await freePort() + const pod = `opencode-session-${hash}` + + // Use the system default kubeconfig (not the SA kubeconfig from KUBECONFIG env var) + // so the port-forward runs with admin credentials, not the restricted service account. + const env = { ...process.env } + delete env.KUBECONFIG + + const proc = spawn( + "kubectl", + ["port-forward", `pod/${pod}`, `${port}:${config.opencodePort}`, "-n", config.namespace], + { stdio: ["ignore", "pipe", "pipe"], env }, + ) + + const ready = new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error("port-forward timeout")), 10_000) + let stderr = "" + + proc.stdout?.on("data", (chunk: Buffer) => { + const line = chunk.toString() + // kubectl prints "Forwarding from 127.0.0.1: -> " when ready + if (line.includes("Forwarding from")) { + clearTimeout(timeout) + resolve(port) + } + }) + + proc.stderr?.on("data", (chunk: Buffer) => { + stderr += chunk.toString() + }) + + proc.on("exit", (code) => { + clearTimeout(timeout) + forwards.delete(hash) + if (code !== 0) reject(new Error(`port-forward exited ${code}: ${stderr}`)) + }) + }) + + const fwd: Forward = { port, proc, ready } + forwards.set(hash, fwd) + + try { + const resolved = await ready + return `http://localhost:${resolved}` + } catch (err) { + console.error(`[dev-proxy] port-forward failed for ${hash}:`, err) + forwards.delete(hash) + return null + } +} + +/** Kill all port-forward processes (called on shutdown). */ +export function cleanup(): void { + for (const [, fwd] of forwards) { + fwd.proc.kill() + } + forwards.clear() +} diff --git a/packages/opencode-router/src/index.ts b/packages/opencode-router/src/index.ts index 709a2980ed64..ce4d0ac1eaaf 100644 --- a/packages/opencode-router/src/index.ts +++ b/packages/opencode-router/src/index.ts @@ -1,9 +1,10 @@ -import http from "node:http"; -import httpProxy from "http-proxy"; -import { handleApi } from "./api.js"; -import { config } from "./config.js"; -import { deleteIdlePods, getPodIP, updateLastActivity } from "./pod-manager.js"; -import { serveStatic } from "./static.js"; +import http from "node:http" +import httpProxy from "http-proxy" +import { handleApi } from "./api.js" +import { config } from "./config.js" +import * as devProxy from "./dev-proxy.js" +import { deleteIdlePods, getPodIP, updateLastActivity } from "./pod-manager.js" +import { serveStatic } from "./static.js" /** * Extract session hash from the Host header. @@ -20,134 +21,146 @@ import { serveStatic } from "./static.js"; function getSessionHash(host: string): string | null { // Strip port from both the incoming Host header and routerDomain before comparing, // so that "abc123-oc.localhost:3002" correctly matches routerDomain "localhost:3002". - const hostname = host.split(":")[0]; - const routerHostname = config.routerDomain.split(":")[0]; + const hostname = host.split(":")[0] + const routerHostname = config.routerDomain.split(":")[0] // Full suffix to strip: e.g. "-oc.no-panic.org" or ".localhost" - const suffix = `${config.routeSuffix}.${routerHostname}`; - if (!hostname.endsWith(suffix)) return null; - const sub = hostname.slice(0, hostname.length - suffix.length); + const suffix = `${config.routeSuffix}.${routerHostname}` + if (!hostname.endsWith(suffix)) return null + const sub = hostname.slice(0, hostname.length - suffix.length) // Must be exactly a 12-char hex session hash - if (/^[a-f0-9]{12}$/.test(sub)) return sub; - return null; + if (/^[a-f0-9]{12}$/.test(sub)) return sub + return null } function getEmail(req: http.IncomingMessage): string | null { - const header = req.headers["x-auth-request-email"]; - if (typeof header === "string" && header.length > 0) return header; + const header = req.headers["x-auth-request-email"] + if (typeof header === "string" && header.length > 0) return header // Dev fallback: use DEV_EMAIL env var when running locally without oauth2-proxy - if (config.devEmail) return config.devEmail; - return null; + if (config.devEmail) return config.devEmail + return null } -const proxy = httpProxy.createProxyServer({}); +const proxy = httpProxy.createProxyServer({}) proxy.on("error", (err, _req, res) => { - console.error("Proxy error:", err.message); + console.error("Proxy error:", err.message) if (res instanceof http.ServerResponse && !res.headersSent) { - res.writeHead(502).end("Bad Gateway"); + res.writeHead(502).end("Bad Gateway") } -}); +}) const server = http.createServer(async (req, res) => { - const email = getEmail(req); + const email = getEmail(req) if (!email) { - res.writeHead(401, { "Content-Type": "text/plain" }).end("Missing user identity"); - return; + res.writeHead(401, { "Content-Type": "text/plain" }).end("Missing user identity") + return } try { - const host = req.headers.host ?? ""; - const sessionHash = getSessionHash(host); + const host = req.headers.host ?? "" + const sessionHash = getSessionHash(host) if (sessionHash) { // ── Session subdomain: .opencode-router.domain ────────────────── // All traffic (HTTP + WS) is proxied directly to the session's pod. // No path stripping — opencode is rooted at /. - const ip = await getPodIP(sessionHash); - if (!ip) { + let target: string | null = null + if (devProxy.enabled) { + // Dev mode: pod IPs aren't routable; use kubectl port-forward + target = await devProxy.target(sessionHash) + } else { + const ip = await getPodIP(sessionHash) + if (ip) target = `http://${ip}:${config.opencodePort}` + } + if (!target) { res .writeHead(503, { "Content-Type": "application/json" }) - .end(JSON.stringify({ error: "session not ready", hash: sessionHash })); - return; + .end(JSON.stringify({ error: "session not ready", hash: sessionHash })) + return } - updateLastActivity(sessionHash); - const target = config.devPodProxyTarget ?? `http://${ip}:${config.opencodePort}`; - proxy.web(req, res, { target }); - return; + updateLastActivity(sessionHash) + proxy.web(req, res, { target }) + return } // ── Root domain: opencode-router.domain ─────────────────────────────── // Router's own API - const url = req.url ?? "/"; + const url = req.url ?? "/" if (url.startsWith("/api/")) { - const handled = await handleApi(req, res, email); - if (handled) return; + const handled = await handleApi(req, res, email) + if (handled) return } // Setup SPA (or Vite dev server) if (config.devViteUrl) { - proxy.web(req, res, { target: config.devViteUrl }); + proxy.web(req, res, { target: config.devViteUrl }) } else { - serveStatic(config.publicDir, req, res); + serveStatic(config.publicDir, req, res) } } catch (err) { - console.error(`Error handling request for ${req.headers.host}:`, err); + console.error(`Error handling request for ${req.headers.host}:`, err) if (!res.headersSent) { - res.writeHead(500, { "Content-Type": "text/plain" }).end("Internal server error"); + res.writeHead(500, { "Content-Type": "text/plain" }).end("Internal server error") } } -}); +}) server.on("upgrade", async (req, socket, head) => { - const email = getEmail(req); + const email = getEmail(req) if (!email) { - socket.destroy(); - return; + socket.destroy() + return } try { - const host = req.headers.host ?? ""; - const sessionHash = getSessionHash(host); + const host = req.headers.host ?? "" + const sessionHash = getSessionHash(host) if (sessionHash) { // WebSocket on a session subdomain — proxy to the pod - const ip = await getPodIP(sessionHash); - if (!ip) { - socket.destroy(); - return; + let target: string | null = null + if (devProxy.enabled) { + target = await devProxy.target(sessionHash) + } else { + const ip = await getPodIP(sessionHash) + if (ip) target = `http://${ip}:${config.opencodePort}` + } + if (!target) { + socket.destroy() + return } - updateLastActivity(sessionHash); - const target = config.devPodProxyTarget ?? `http://${ip}:${config.opencodePort}`; - proxy.ws(req, socket, head, { target }); - return; + updateLastActivity(sessionHash) + proxy.ws(req, socket, head, { target }) + return } // WebSocket on root domain — Vite HMR in dev mode if (config.devViteUrl) { - proxy.ws(req, socket, head, { target: config.devViteUrl }); - return; + proxy.ws(req, socket, head, { target: config.devViteUrl }) + return } - socket.destroy(); + socket.destroy() } catch (err) { - console.error(`WebSocket upgrade error for ${req.headers.host}:`, err); - socket.destroy(); + console.error(`WebSocket upgrade error for ${req.headers.host}:`, err) + socket.destroy() } -}); +}) // Background: clean up idle pods every 60 seconds -const cleanupInterval = setInterval(deleteIdlePods, 60_000); +const cleanupInterval = setInterval(deleteIdlePods, 60_000) function shutdown() { - console.log("Shutting down..."); - clearInterval(cleanupInterval); - server.close(() => process.exit(0)); - setTimeout(() => process.exit(1), 10_000); + console.log("Shutting down...") + clearInterval(cleanupInterval) + devProxy.cleanup() + server.close(() => process.exit(0)) + setTimeout(() => process.exit(1), 10_000) } -process.on("SIGTERM", shutdown); -process.on("SIGINT", shutdown); +process.on("SIGTERM", shutdown) +process.on("SIGINT", shutdown) server.listen(config.port, () => { - console.log(`opencode-router listening on :${config.port} | domain: ${config.routerDomain}`); -}); + console.log(`opencode-router listening on :${config.port} | domain: ${config.routerDomain}`) +}) diff --git a/packages/opencode-router/src/pod-manager.ts b/packages/opencode-router/src/pod-manager.ts index 69602c7f4305..72b8f74b5ae9 100644 --- a/packages/opencode-router/src/pod-manager.ts +++ b/packages/opencode-router/src/pod-manager.ts @@ -1,43 +1,53 @@ -import crypto from "node:crypto"; -import fs from "node:fs"; -import * as k8s from "@kubernetes/client-node"; -import { config } from "./config.js"; +import crypto from "node:crypto" +import fs from "node:fs" +import * as k8s from "@kubernetes/client-node" +import humanId from "human-id" +import { config } from "./config.js" -const kc = new k8s.KubeConfig(); +const kc = new k8s.KubeConfig() // loadFromCluster() does not throw when not in a pod — it silently produces // an invalid config with server=undefined. Check for the SA token file first. if (fs.existsSync("/var/run/secrets/kubernetes.io/serviceaccount/token")) { - kc.loadFromCluster(); + kc.loadFromCluster() } else { - kc.loadFromDefault(); + kc.loadFromDefault() } -const k8sApi = kc.makeApiClient(k8s.CoreV1Api); +const k8sApi = kc.makeApiClient(k8s.CoreV1Api) -const LABEL_SESSION_HASH = "opencode.ai/session-hash"; -const LABEL_MANAGED_BY = "app.kubernetes.io/managed-by"; -const MANAGED_BY_VALUE = "opencode-router"; -const ANNOTATION_LAST_ACTIVITY = "opencode.ai/last-activity"; -const ANNOTATION_USER_EMAIL = "opencode.ai/user-email"; -const ANNOTATION_REPO_URL = "opencode.ai/repo-url"; -const ANNOTATION_BRANCH = "opencode.ai/branch"; +const LABEL_SESSION_HASH = "opencode.ai/session-hash" +const LABEL_MANAGED_BY = "app.kubernetes.io/managed-by" +const MANAGED_BY_VALUE = "opencode-router" +const ANNOTATION_LAST_ACTIVITY = "opencode.ai/last-activity" +const ANNOTATION_USER_EMAIL = "opencode.ai/user-email" +const ANNOTATION_REPO_URL = "opencode.ai/repo-url" +const ANNOTATION_BRANCH = "opencode.ai/branch" +const ANNOTATION_SOURCE_BRANCH = "opencode.ai/source-branch" /** In-memory throttle for annotation updates: hash → last update epoch ms */ -const activityThrottle = new Map(); -const THROTTLE_MS = 60_000; +const activityThrottle = new Map() +const THROTTLE_MS = 60_000 export interface SessionKey { - email: string; - repoUrl: string; - branch: string; + email: string + repoUrl: string + /** Session branch — auto-generated unique name (e.g. "calm-snails-dream"). Forms part of the session identity hash. */ + branch: string + /** Source branch the user starts from (e.g. "main"). Used by git-init to set the starting point. */ + sourceBranch: string } export interface SessionInfo { - hash: string; - email: string; - repoUrl: string; - branch: string; - state: PodState; - url: string; + hash: string + email: string + repoUrl: string + /** Session branch (auto-generated, e.g. "calm-snails-dream") */ + branch: string + /** Source branch the session was created from (e.g. "main") */ + sourceBranch: string + state: PodState + url: string + lastActivity: string + idleTimeoutMinutes: number } /** @@ -45,37 +55,37 @@ export interface SessionInfo { * This is the stable identity for a session — used for pod name, PVC name, and URL slug. */ export function getSessionHash(email: string, repoUrl: string, branch: string): string { - const key = `${email.toLowerCase().trim()}:${repoUrl.trim()}:${branch.trim()}`; - return crypto.createHash("sha256").update(key).digest("hex").slice(0, 12); + const key = `${email.toLowerCase().trim()}:${repoUrl.trim()}:${branch.trim()}` + return crypto.createHash("sha256").update(key).digest("hex").slice(0, 12) } function podName(hash: string): string { - return `opencode-session-${hash}`; + return `opencode-session-${hash}` } function pvcName(hash: string): string { - return `opencode-pvc-${hash}`; + return `opencode-pvc-${hash}` } function sessionLabels(hash: string): Record { return { [LABEL_SESSION_HASH]: hash, [LABEL_MANAGED_BY]: MANAGED_BY_VALUE, - }; + } } /** * Create PVC for a session if it doesn't exist. Idempotent. */ export async function ensurePVC(session: SessionKey): Promise { - const hash = getSessionHash(session.email, session.repoUrl, session.branch); - const name = pvcName(hash); + const hash = getSessionHash(session.email, session.repoUrl, session.branch) + const name = pvcName(hash) try { - await k8sApi.readNamespacedPersistentVolumeClaim({ name, namespace: config.namespace }); - return; // already exists + await k8sApi.readNamespacedPersistentVolumeClaim({ name, namespace: config.namespace }) + return // already exists } catch (err) { - if (!isNotFound(err)) throw err; + if (!isNotFound(err)) throw err } const pvc: k8s.V1PersistentVolumeClaim = { @@ -87,6 +97,7 @@ export async function ensurePVC(session: SessionKey): Promise { [ANNOTATION_USER_EMAIL]: session.email, [ANNOTATION_REPO_URL]: session.repoUrl, [ANNOTATION_BRANCH]: session.branch, + [ANNOTATION_SOURCE_BRANCH]: session.sourceBranch, }, }, spec: { @@ -96,29 +107,29 @@ export async function ensurePVC(session: SessionKey): Promise { requests: { storage: config.storageSize }, }, }, - }; + } try { - await k8sApi.createNamespacedPersistentVolumeClaim({ namespace: config.namespace, body: pvc }); + await k8sApi.createNamespacedPersistentVolumeClaim({ namespace: config.namespace, body: pvc }) } catch (err) { - if (!isConflict(err)) throw err; + if (!isConflict(err)) throw err } } -export type PodState = "none" | "creating" | "running"; +export type PodState = "none" | "creating" | "running" | "stopped" /** * Return the current state of a session's pod by its hash. */ export async function getPodState(hash: string): Promise { - const name = podName(hash); + const name = podName(hash) try { - const pod = await k8sApi.readNamespacedPod({ name, namespace: config.namespace }); - if (pod.status?.phase === "Running" && pod.status.podIP) return "running"; - return "creating"; + const pod = await k8sApi.readNamespacedPod({ name, namespace: config.namespace }) + if (pod.status?.phase === "Running" && pod.status.podIP) return "running" + return "creating" } catch (err) { - if (isNotFound(err)) return "none"; - throw err; + if (isNotFound(err)) return "none" + throw err } } @@ -130,24 +141,24 @@ export async function getPodState(hash: string): Promise { * - Checks out `branch` if it exists remotely, otherwise creates it from the default branch */ export async function ensurePod(session: SessionKey): Promise { - const hash = getSessionHash(session.email, session.repoUrl, session.branch); - const name = podName(hash); + const hash = getSessionHash(session.email, session.repoUrl, session.branch) + const name = podName(hash) try { - await k8sApi.readNamespacedPod({ name, namespace: config.namespace }); - return hash; // already exists + await k8sApi.readNamespacedPod({ name, namespace: config.namespace }) + return hash // already exists } catch (err) { - if (!isNotFound(err)) throw err; + if (!isNotFound(err)) throw err } - const now = new Date().toISOString(); - const { repoUrl, branch, email } = session; + const now = new Date().toISOString() + const { repoUrl, branch, sourceBranch, email } = session // The repo is cloned directly into /workspace (init container) which maps to /projects/ // via subPath "projects". The main container mounts the full PVC at /home/opencode (no subPath), // so the cloned repo is at /home/opencode/projects/ inside the main container. We set workingDir // accordingly so opencode serve starts in the git repo and correctly discovers the project. - const workspacePath = `/home/opencode/projects`; + const workspacePath = `/home/opencode/projects` // Use GIT_SAFE="git -c safe.directory=/workspace" to avoid needing a writable $HOME // for "git config --global". The -c flag applies the config inline for each invocation. @@ -159,13 +170,15 @@ export async function ensurePod(session: SessionKey): Promise { `fi`, `cd /workspace`, `$GIT fetch --all`, - // Check out existing remote branch, or create a new one from the current HEAD - `if $GIT ls-remote --exit-code --heads origin "${branch}" > /dev/null 2>&1; then`, - ` $GIT checkout -B "${branch}" "origin/${branch}"`, + // Switch to session branch: if it already exists locally (resume), just check it out. + // Otherwise establish the source branch as starting point and create the session branch. + `if $GIT rev-parse --verify "${branch}" >/dev/null 2>&1; then`, + ` $GIT checkout "${branch}"`, `else`, + ` $GIT checkout -B "${sourceBranch}" "origin/${sourceBranch}"`, ` $GIT checkout -b "${branch}"`, `fi`, - ].join("\n"); + ].join("\n") const initContainers: k8s.V1Container[] = [ { @@ -187,7 +200,7 @@ export async function ensurePod(session: SessionKey): Promise { ], volumeMounts: [{ name: "user-data", mountPath: "/workspace", subPath: "projects" }], }, - ]; + ] const pod: k8s.V1Pod = { metadata: { @@ -199,6 +212,7 @@ export async function ensurePod(session: SessionKey): Promise { [ANNOTATION_USER_EMAIL]: email, [ANNOTATION_REPO_URL]: repoUrl, [ANNOTATION_BRANCH]: branch, + [ANNOTATION_SOURCE_BRANCH]: sourceBranch, }, }, spec: { @@ -210,9 +224,7 @@ export async function ensurePod(session: SessionKey): Promise { runAsNonRoot: true, seccompProfile: { type: "RuntimeDefault" }, }, - ...(config.imagePullSecretName - ? { imagePullSecrets: [{ name: config.imagePullSecretName }] } - : {}), + ...(config.imagePullSecretName ? { imagePullSecrets: [{ name: config.imagePullSecretName }] } : {}), initContainers, containers: [ { @@ -223,14 +235,7 @@ export async function ensurePod(session: SessionKey): Promise { // The main container mounts the full PVC at /home/opencode, so the repo is at // /home/opencode/projects/. workingDir: workspacePath, - command: [ - "opencode", - "serve", - "--hostname", - "0.0.0.0", - "--port", - String(config.opencodePort), - ], + command: ["opencode", "serve", "--hostname", "0.0.0.0", "--port", String(config.opencodePort)], ports: [{ containerPort: config.opencodePort }], envFrom: [{ secretRef: { name: config.apiKeySecretName } }], securityContext: { @@ -256,29 +261,29 @@ export async function ensurePod(session: SessionKey): Promise { }, ], }, - }; + } try { - await k8sApi.createNamespacedPod({ namespace: config.namespace, body: pod }); + await k8sApi.createNamespacedPod({ namespace: config.namespace, body: pod }) } catch (err) { - if (!isConflict(err)) throw err; + if (!isConflict(err)) throw err } - return hash; + return hash } /** * Return the pod's IP if it is Running, or null otherwise. Looks up by session hash. */ export async function getPodIP(hash: string): Promise { - const name = podName(hash); + const name = podName(hash) try { - const pod = await k8sApi.readNamespacedPod({ name, namespace: config.namespace }); - if (pod.status?.phase === "Running" && pod.status.podIP) return pod.status.podIP; - return null; + const pod = await k8sApi.readNamespacedPod({ name, namespace: config.namespace }) + if (pod.status?.phase === "Running" && pod.status.podIP) return pod.status.podIP + return null } catch (err) { - if (isNotFound(err)) return null; - throw err; + if (isNotFound(err)) return null + throw err } } @@ -288,39 +293,61 @@ export async function getPodIP(hash: string): Promise { */ export async function listUserSessions( email: string, - req: import("node:http").IncomingMessage + req: import("node:http").IncomingMessage, ): Promise { - const response = await k8sApi.listNamespacedPod({ + const proto = (req.headers["x-forwarded-proto"] as string | undefined) ?? "http" + + const pvcList = await k8sApi.listNamespacedPersistentVolumeClaim({ namespace: config.namespace, labelSelector: `${LABEL_MANAGED_BY}=${MANAGED_BY_VALUE}`, - }); + }) - const proto = (req.headers["x-forwarded-proto"] as string | undefined) ?? "http"; + const userPVCs = pvcList.items.filter( + (pvc: k8s.V1PersistentVolumeClaim) => pvc.metadata?.annotations?.[ANNOTATION_USER_EMAIL] === email, + ) + if (userPVCs.length === 0) return [] - const sessions: SessionInfo[] = []; - for (const pod of response.items) { - const ann = pod.metadata?.annotations ?? {}; - if (ann[ANNOTATION_USER_EMAIL] !== email) continue; + const podList = await k8sApi.listNamespacedPod({ + namespace: config.namespace, + labelSelector: `${LABEL_MANAGED_BY}=${MANAGED_BY_VALUE}`, + }) + const podMap = new Map() + for (const pod of podList.items) { + const h = pod.metadata?.labels?.[LABEL_SESSION_HASH] + if (h) podMap.set(h, pod) + } - const hash = pod.metadata?.labels?.[LABEL_SESSION_HASH]; - if (!hash) continue; + return userPVCs.map((pvc: k8s.V1PersistentVolumeClaim) => { + const ann = pvc.metadata?.annotations ?? {} + const hash = pvc.metadata?.labels?.[LABEL_SESSION_HASH] ?? "" + const pod = podMap.get(hash) + + let state: PodState + if (!pod) { + state = "stopped" + } else if (pod.status?.phase === "Running" && pod.status?.podIP) { + state = "running" + } else { + state = "creating" + } - const repoUrl = ann[ANNOTATION_REPO_URL] ?? ""; - const branch = ann[ANNOTATION_BRANCH] ?? ""; - const state: PodState = - pod.status?.phase === "Running" && pod.status.podIP ? "running" : "creating"; + const lastActivity = + pod?.metadata?.annotations?.[ANNOTATION_LAST_ACTIVITY] ?? + ann[ANNOTATION_LAST_ACTIVITY] ?? + new Date().toISOString() - sessions.push({ + return { hash, email, - repoUrl, - branch, + repoUrl: ann[ANNOTATION_REPO_URL] ?? "", + branch: ann[ANNOTATION_BRANCH] ?? "", + sourceBranch: ann[ANNOTATION_SOURCE_BRANCH] ?? "", state, url: `${proto}://${hash}${config.routeSuffix}.${config.routerDomain}`, - }); - } - - return sessions; + lastActivity, + idleTimeoutMinutes: config.idleTimeoutMinutes, + } + }) } /** @@ -328,12 +355,12 @@ export async function listUserSessions( * Throttled to at most once per minute per session to reduce K8s API load. */ export function updateLastActivity(hash: string): void { - const now = Date.now(); - const last = activityThrottle.get(hash) ?? 0; - if (now - last < THROTTLE_MS) return; + const now = Date.now() + const last = activityThrottle.get(hash) ?? 0 + if (now - last < THROTTLE_MS) return - activityThrottle.set(hash, now); - const name = podName(hash); + activityThrottle.set(hash, now) + const name = podName(hash) k8sApi .patchNamespacedPod({ @@ -346,8 +373,8 @@ export function updateLastActivity(hash: string): void { }, }) .catch((err) => { - console.error(`Failed to update last-activity for ${name}:`, err); - }); + console.error(`Failed to update last-activity for ${name}:`, err) + }) } /** @@ -355,34 +382,90 @@ export function updateLastActivity(hash: string): void { * Only deletes pods — PVCs are preserved. */ export async function deleteIdlePods(): Promise { - const cutoff = Date.now() - config.idleTimeoutMinutes * 60_000; + const cutoff = Date.now() - config.idleTimeoutMinutes * 60_000 try { const response = await k8sApi.listNamespacedPod({ namespace: config.namespace, labelSelector: `${LABEL_MANAGED_BY}=${MANAGED_BY_VALUE}`, - }); + }) for (const pod of response.items) { - const lastActivity = pod.metadata?.annotations?.[ANNOTATION_LAST_ACTIVITY]; - if (!lastActivity) continue; + const lastActivity = pod.metadata?.annotations?.[ANNOTATION_LAST_ACTIVITY] + if (!lastActivity) continue - const lastMs = new Date(lastActivity).getTime(); + const lastMs = new Date(lastActivity).getTime() if (lastMs < cutoff) { - const name = pod.metadata?.name; - if (!name) continue; - console.log(`Deleting idle pod ${name} (last activity: ${lastActivity})`); + const name = pod.metadata?.name + if (!name) continue + console.log(`Deleting idle pod ${name} (last activity: ${lastActivity})`) await k8sApi .deleteNamespacedPod({ name, namespace: config.namespace }) - .catch((err) => console.error(`Failed to delete pod ${name}:`, err)); + .catch((err) => console.error(`Failed to delete pod ${name}:`, err)) - const hash = pod.metadata?.labels?.[LABEL_SESSION_HASH]; - if (hash) activityThrottle.delete(hash); + const hash = pod.metadata?.labels?.[LABEL_SESSION_HASH] + if (hash) activityThrottle.delete(hash) } } } catch (err) { - console.error("Failed to list pods for idle cleanup:", err); + console.error("Failed to list pods for idle cleanup:", err) + } +} + +/** + * Terminate a session: delete pod (if present) + PVC. Irreversible. + * Only the session owner (email matching PVC annotation) may terminate. + */ +export async function terminateSession(hash: string, email: string): Promise { + const name = pvcName(hash) + let pvc: k8s.V1PersistentVolumeClaim + try { + pvc = await k8sApi.readNamespacedPersistentVolumeClaim({ name, namespace: config.namespace }) + } catch (err) { + if (isNotFound(err)) throw new Error("NotFound") + throw err } + + const owner = pvc.metadata?.annotations?.[ANNOTATION_USER_EMAIL] + if (owner !== email) throw new Error("Forbidden") + + // Delete pod if it exists (ignore NotFound) + await k8sApi.deleteNamespacedPod({ name: podName(hash), namespace: config.namespace }).catch((err) => { + if (!isNotFound(err)) throw err + }) + + // Delete PVC + await k8sApi.deleteNamespacedPersistentVolumeClaim({ name, namespace: config.namespace }) + + activityThrottle.delete(hash) +} + +/** + * Resume a stopped session: recreate the pod for an existing PVC. + * Idempotent — safe to call when pod already exists. + * Only the session owner may resume. + */ +export async function resumeSession(hash: string, email: string): Promise { + const name = pvcName(hash) + let pvc: k8s.V1PersistentVolumeClaim + try { + pvc = await k8sApi.readNamespacedPersistentVolumeClaim({ name, namespace: config.namespace }) + } catch (err) { + if (isNotFound(err)) throw new Error("NotFound") + throw err + } + + const ann = pvc.metadata?.annotations ?? {} + if (ann[ANNOTATION_USER_EMAIL] !== email) throw new Error("Forbidden") + + const session: SessionKey = { + email: ann[ANNOTATION_USER_EMAIL] ?? email, + repoUrl: ann[ANNOTATION_REPO_URL] ?? "", + branch: ann[ANNOTATION_BRANCH] ?? "", + sourceBranch: ann[ANNOTATION_SOURCE_BRANCH] ?? "", + } + + await ensurePod(session) } function hasCode(err: unknown): err is { code: number } { @@ -391,13 +474,34 @@ function hasCode(err: unknown): err is { code: number } { err !== null && "code" in err && typeof (err as Record).code === "number" - ); + ) } function isNotFound(err: unknown): boolean { - return hasCode(err) && err.code === 404; + return hasCode(err) && err.code === 404 } function isConflict(err: unknown): boolean { - return hasCode(err) && err.code === 409; + return hasCode(err) && err.code === 409 +} + +/** + * Generate a collision-safe human-readable branch name. + * Checks existing PVCs to avoid reusing a name for the same (email, repoUrl) pair. + * Caps at 10 iterations; returns last candidate if all collide. + */ +export async function suggestBranch(email: string, repoUrl: string): Promise { + let candidate = "" + for (let i = 0; i < 10; i++) { + candidate = (humanId as unknown as (opts?: object) => string)({ separator: "-", capitalize: false }) + const hash = getSessionHash(email, repoUrl, candidate) + try { + await k8sApi.readNamespacedPersistentVolumeClaim({ name: pvcName(hash), namespace: config.namespace }) + // PVC exists — collision, try next + } catch (err) { + if (isNotFound(err)) return candidate + throw err + } + } + return candidate } From 9babd1d496dd9a5a3448b2fedf0327e38c0ee06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Mon, 13 Apr 2026 09:33:35 +0200 Subject: [PATCH 004/616] =?UTF-8?q?feat(router-app):=20session=20lifecycle?= =?UTF-8?q?=20UI=20=E2=80=94=20stopped=20sessions,=20resume,=20terminate?= =?UTF-8?q?=20with=20confirm=20dialog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Session list shows stopped sessions (PVC exists, no pod) as non-clickable cards - Resume button transitions to LoadingScreen which polls and auto-redirects on running - Terminate button opens a confirm dialog (Dialog + DialogProvider) before destructive delete - Terminate button shows 'Terminating…' and is disabled while API call is in flight - Idle status label: 'stops in ~Xm' for running, 'stopped on DD/MM/YYYY' for stopped - Session list auto-polls every 5 s; polling is paused while LoadingScreen is active (prevents the 5 s timer from overwriting the creating phase and killing the redirect) - sourceBranch/sessionBranch split reflected in SetupForm: user enters source branch, session branch is auto-generated via suggest-branch and shown display-only --- packages/opencode-router-app/bunfig.toml | 3 + packages/opencode-router-app/package.json | 7 +- packages/opencode-router-app/src/api.ts | 81 ++++--- packages/opencode-router-app/src/app.tsx | 197 +++++++++++++----- packages/opencode-router-app/src/entry.tsx | 10 +- .../src/loading-screen.tsx | 27 +-- .../opencode-router-app/src/session-utils.ts | 35 ++++ .../src/setup-form-utils.ts | 20 ++ .../opencode-router-app/src/setup-form.tsx | 131 ++++++++---- packages/opencode-router-app/tsconfig.json | 2 +- 10 files changed, 375 insertions(+), 138 deletions(-) create mode 100644 packages/opencode-router-app/bunfig.toml create mode 100644 packages/opencode-router-app/src/session-utils.ts create mode 100644 packages/opencode-router-app/src/setup-form-utils.ts diff --git a/packages/opencode-router-app/bunfig.toml b/packages/opencode-router-app/bunfig.toml new file mode 100644 index 000000000000..79e045a694f4 --- /dev/null +++ b/packages/opencode-router-app/bunfig.toml @@ -0,0 +1,3 @@ +[test] +conditions = ["browser"] +root = "./src" diff --git a/packages/opencode-router-app/package.json b/packages/opencode-router-app/package.json index 6d1278227be6..42535161c260 100644 --- a/packages/opencode-router-app/package.json +++ b/packages/opencode-router-app/package.json @@ -6,7 +6,11 @@ "scripts": { "dev": "vite", "build": "vite build", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "test": "bun test src/", + "e2e": "playwright test", + "e2e:ui": "playwright test --ui", + "e2e:headed": "playwright test --headed" }, "dependencies": { "@opencode-ai/ui": "workspace:*", @@ -16,6 +20,7 @@ "devDependencies": { "@tailwindcss/vite": "catalog:", "@tsconfig/node22": "catalog:", + "bun-types": "1.3.12", "tailwindcss": "catalog:", "typescript": "catalog:", "vite": "catalog:", diff --git a/packages/opencode-router-app/src/api.ts b/packages/opencode-router-app/src/api.ts index fc3b8e5f179e..d699429ff2ee 100644 --- a/packages/opencode-router-app/src/api.ts +++ b/packages/opencode-router-app/src/api.ts @@ -1,50 +1,79 @@ export interface Session { - hash: string; - email: string; - repoUrl: string; - branch: string; - state: "creating" | "running"; - url: string; + hash: string + email: string + repoUrl: string + /** Session branch — auto-generated unique name (e.g. "calm-snails-dream") */ + branch: string + /** Source branch the session was created from (e.g. "main") */ + sourceBranch: string + state: "creating" | "running" | "stopped" + url: string + lastActivity: string + idleTimeoutMinutes: number } export interface SessionsResponse { - email: string; - sessions: Session[]; + email: string + sessions: Session[] } export async function listSessions(): Promise { - const res = await fetch("/api/sessions"); - if (!res.ok) throw new Error(`Failed to list sessions: ${res.status}`); - return res.json(); + const res = await fetch("/api/sessions") + if (!res.ok) throw new Error(`Failed to list sessions: ${res.status}`) + return res.json() } export interface CreateSessionResponse { - hash: string; - url: string; - state: "creating"; - error?: string; + hash: string + url: string + state: "creating" + error?: string } export async function createSession( repoUrl: string, - branch: string + branch: string, + sourceBranch: string, ): Promise { const res = await fetch("/api/sessions", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ repoUrl, branch }), - }); + body: JSON.stringify({ repoUrl, branch, sourceBranch }), + }) if (!res.ok) { - const body = await res.json().catch(() => ({})); - throw new Error(body.error ?? `Failed to create session: ${res.status}`); + const body = await res.json().catch(() => ({})) + throw new Error(body.error ?? `Failed to create session: ${res.status}`) } - return res.json(); + return res.json() } export async function getSessionState( - hash: string -): Promise<{ hash: string; state: "creating" | "running"; url: string }> { - const res = await fetch(`/api/sessions/${hash}`); - if (!res.ok) throw new Error(`Failed to get session state: ${res.status}`); - return res.json(); + hash: string, +): Promise<{ hash: string; state: "creating" | "running" | "stopped"; url: string }> { + const res = await fetch(`/api/sessions/${hash}`) + if (!res.ok) throw new Error(`Failed to get session state: ${res.status}`) + return res.json() +} + +export async function terminateSession(hash: string): Promise { + const res = await fetch(`/api/sessions/${hash}`, { method: "DELETE" }) + if (!res.ok) { + const body = await res.json().catch(() => ({})) + throw new Error((body as any).error ?? `Failed to terminate session: ${res.status}`) + } +} + +export async function resumeSession(hash: string): Promise { + const res = await fetch(`/api/sessions/${hash}/resume`, { method: "POST" }) + if (!res.ok) { + const body = await res.json().catch(() => ({})) + throw new Error((body as any).error ?? `Failed to resume session: ${res.status}`) + } +} + +export async function suggestBranch(repoUrl: string): Promise<{ branch: string }> { + const params = new URLSearchParams({ repoUrl }) + const res = await fetch(`/api/sessions/suggest-branch?${params}`) + if (!res.ok) throw new Error(`Failed to suggest branch: ${res.status}`) + return res.json() } diff --git a/packages/opencode-router-app/src/app.tsx b/packages/opencode-router-app/src/app.tsx index e4332e5dba59..a98154965c57 100644 --- a/packages/opencode-router-app/src/app.tsx +++ b/packages/opencode-router-app/src/app.tsx @@ -1,46 +1,61 @@ -import { Button } from "@opencode-ai/ui/button"; -import { Logo } from "@opencode-ai/ui/logo"; -import { For, Match, Show, Switch, createSignal, onMount } from "solid-js"; -import { type Session, listSessions } from "./api"; -import { LoadingScreen } from "./loading-screen"; -import { SetupForm } from "./setup-form"; +import { Button } from "@opencode-ai/ui/button" +import { Logo } from "@opencode-ai/ui/logo" +import { Dialog } from "@opencode-ai/ui/dialog" +import { useDialog, useI18n } from "@opencode-ai/ui/context" +import { For, Match, Show, Switch, createSignal, onCleanup, onMount } from "solid-js" +import { type Session, listSessions, resumeSession, terminateSession } from "./api" +import { useT } from "./i18n" +import { LoadingScreen } from "./loading-screen" +import { computeIdleStatus } from "./session-utils" +import { SetupForm } from "./setup-form" type Phase = | { kind: "loading" } | { kind: "list"; sessions: Session[]; email: string } | { kind: "new-session"; email: string } | { kind: "creating"; hash: string; url: string } - | { kind: "error"; message: string }; + | { kind: "error"; message: string } export function App() { - const [phase, setPhase] = createSignal({ kind: "loading" }); + const [phase, setPhase] = createSignal({ kind: "loading" }) + const [terminating, setTerminating] = createSignal>(new Set()) + const dialog = useDialog() + const t = useT(useI18n()) const loadSessions = async () => { try { - const { email, sessions } = await listSessions(); - setPhase({ kind: "list", sessions, email }); + const { email, sessions } = await listSessions() + setPhase({ kind: "list", sessions, email }) } catch (err) { setPhase({ kind: "error", - message: err instanceof Error ? err.message : "Failed to connect", - }); + message: err instanceof Error ? err.message : t("app.error.connect"), + }) } - }; + } - onMount(loadSessions); + const pollSessions = () => { + // Only refresh the list when we're actually showing the list — never interrupt + // the LoadingScreen (creating phase) which manages its own redirect loop. + const p = phase() + if (p.kind === "list" || p.kind === "loading") loadSessions() + } + + onMount(() => { + loadSessions() + const timer = setInterval(pollSessions, 5_000) + onCleanup(() => clearInterval(timer)) + }) return ( -
+

- Loading... + {t("app.loading")}

@@ -49,34 +64,117 @@ export function App() {

- Signed in as {p().email || "—"} + {t("app.signedInAs", { email: p().email || "—" })}

0}>

- Your sessions + {t("app.yourSessions")}

- {(session) => ( - -

- {session.repoUrl.replace(/^https?:\/\//, "").replace(/\.git$/, "")} -

-

- {session.branch} · {session.state} -

-
- )} + {(session) => { + const idle = computeIdleStatus( + session.state, + session.lastActivity, + session.idleTimeoutMinutes, + { + stopsIn: (m) => t("session.idle.stopsIn", { minutes: m }), + stoppedOn: (d) => t("session.idle.stoppedOn", { date: d }), + stoppingSoon: t("session.idle.stoppingSoon"), + }, + ) + const handleTerminate = (e: MouseEvent) => { + e.preventDefault() + e.stopPropagation() + const hash = session.hash + const repo = session.repoUrl.replace(/^https?:\/\//, "").replace(/\.git$/, "") + dialog.show(() => ( + +
+ + +
+
+ )) + } + const handleResume = async (e: MouseEvent) => { + e.preventDefault() + e.stopPropagation() + await resumeSession(session.hash) + setPhase({ kind: "creating", hash: session.hash, url: session.url }) + } + const cardStyle = { + background: "var(--background-surface)", + "border-color": "var(--border-base)", + "text-decoration": "none", + } + const inner = ( +
+
+

+ {session.repoUrl.replace(/^https?:\/\//, "").replace(/\.git$/, "")} +

+

+ {session.branch} + {session.state !== "stopped" + ? ` · ${t(`session.state.${session.state}` as "session.state.creating")} · ${idle.label}` + : ` · ${idle.label}`} +

+
+
+ + + + +
+
+ ) + return session.state === "stopped" ? ( +
+ {inner} +
+ ) : ( + + {inner} + + ) + }}
@@ -86,17 +184,13 @@ export function App() { size="large" onClick={() => setPhase({ kind: "new-session", email: p().email })} > - New Session + {t("app.newSession")}
)} - ) - } - > + )}> {(p) => (
- setPhase({ kind: "creating", hash, url })} - /> + setPhase({ kind: "creating", hash, url })} />
)}
- )} - > + )}> {(p) => } @@ -136,5 +225,5 @@ export function App() {
- ); + ) } diff --git a/packages/opencode-router-app/src/entry.tsx b/packages/opencode-router-app/src/entry.tsx index 0b48cb4549ca..4e92b1f73975 100644 --- a/packages/opencode-router-app/src/entry.tsx +++ b/packages/opencode-router-app/src/entry.tsx @@ -1,12 +1,20 @@ import { render } from "solid-js/web" import { ThemeProvider } from "@opencode-ai/ui/theme/context" +import { DialogProvider, I18nProvider } from "@opencode-ai/ui/context" +import { createI18n } from "./i18n" import { App } from "./app" import "./index.css" +const i18n = createI18n(navigator.language || "en") + render( () => ( - + + + + + ), document.getElementById("root")!, diff --git a/packages/opencode-router-app/src/loading-screen.tsx b/packages/opencode-router-app/src/loading-screen.tsx index 922b00e2b6f2..8a6f916417a6 100644 --- a/packages/opencode-router-app/src/loading-screen.tsx +++ b/packages/opencode-router-app/src/loading-screen.tsx @@ -1,24 +1,27 @@ -import { onCleanup, onMount } from "solid-js"; -import { getSessionState } from "./api"; +import { onCleanup, onMount } from "solid-js" +import { useI18n } from "@opencode-ai/ui/context" +import { getSessionState } from "./api" +import { useT } from "./i18n" export function LoadingScreen(props: { hash: string; url: string }) { - let timer: ReturnType; + const t = useT(useI18n()) + let timer: ReturnType onMount(() => { timer = setInterval(async () => { try { - const session = await getSessionState(props.hash); + const session = await getSessionState(props.hash) if (session.state === "running") { - clearInterval(timer); - window.location.replace(props.url); + clearInterval(timer) + window.location.replace(props.url) } } catch { // Retry on next tick } - }, 3000); - }); + }, 3000) + }) - onCleanup(() => clearInterval(timer)); + onCleanup(() => clearInterval(timer)) return (
@@ -30,11 +33,11 @@ export function LoadingScreen(props: { hash: string; url: string }) { }} />

- Starting your OpenCode session... + {t("loading.title")}

- This usually takes a few seconds. + {t("loading.subtitle")}

- ); + ) } diff --git a/packages/opencode-router-app/src/session-utils.ts b/packages/opencode-router-app/src/session-utils.ts new file mode 100644 index 000000000000..2dbf5b4ac21c --- /dev/null +++ b/packages/opencode-router-app/src/session-utils.ts @@ -0,0 +1,35 @@ +export type IdleLabels = { + stopsIn: (minutes: number) => string + stoppedOn: (date: string) => string + stoppingSoon: string +} + +/** + * Compute idle status label for a session. + * - stopped state: "stopped on " + * - running/creating with time remaining: "stops in ~Xm" + * - running/creating past timeout: "stopping soon" + * + * Labels are injected so this function stays pure and locale-independent. + */ +export function computeIdleStatus( + state: "creating" | "running" | "stopped", + lastActivity: string, + idleTimeoutMinutes: number, + labels: IdleLabels, +): { stopsInMinutes: number | null; stoppedMinutesAgo: number | null; label: string } { + if (state === "stopped") { + const date = new Date(lastActivity).toLocaleDateString(undefined, { + month: "2-digit", + day: "2-digit", + year: "numeric", + }) + return { stopsInMinutes: null, stoppedMinutesAgo: null, label: labels.stoppedOn(date) } + } + const elapsed = Math.floor((Date.now() - new Date(lastActivity).getTime()) / 60_000) + const remaining = idleTimeoutMinutes - elapsed + if (remaining >= 0) { + return { stopsInMinutes: remaining, stoppedMinutesAgo: null, label: labels.stopsIn(remaining) } + } + return { stopsInMinutes: null, stoppedMinutesAgo: 0, label: labels.stoppingSoon } +} diff --git a/packages/opencode-router-app/src/setup-form-utils.ts b/packages/opencode-router-app/src/setup-form-utils.ts new file mode 100644 index 000000000000..1dea866c6b73 --- /dev/null +++ b/packages/opencode-router-app/src/setup-form-utils.ts @@ -0,0 +1,20 @@ +const GIT_URL_PATTERN = /^https?:\/\/.+\/.+/ + +export type ValidationErrors = { + repoUrlRequired: string + repoUrlInvalid: string + sourceBranchRequired: string +} + +export function buildSessionKey( + repoUrl: string, + sourceBranch: string, + errors: ValidationErrors, +): { valid: true; repoUrl: string; sourceBranch: string } | { valid: false; error: string } { + const url = repoUrl.trim() + const br = sourceBranch.trim() + if (!url) return { valid: false, error: errors.repoUrlRequired } + if (!GIT_URL_PATTERN.test(url)) return { valid: false, error: errors.repoUrlInvalid } + if (!br) return { valid: false, error: errors.sourceBranchRequired } + return { valid: true, repoUrl: url, sourceBranch: br } +} diff --git a/packages/opencode-router-app/src/setup-form.tsx b/packages/opencode-router-app/src/setup-form.tsx index c43a13502605..b9123a9d3d3c 100644 --- a/packages/opencode-router-app/src/setup-form.tsx +++ b/packages/opencode-router-app/src/setup-form.tsx @@ -1,46 +1,69 @@ -import { Button } from "@opencode-ai/ui/button"; -import { TextField } from "@opencode-ai/ui/text-field"; -import { createSignal } from "solid-js"; -import { createSession } from "./api"; +import { Button } from "@opencode-ai/ui/button" +import { TextField } from "@opencode-ai/ui/text-field" +import { useI18n } from "@opencode-ai/ui/context" +import { Show, createSignal, onMount } from "solid-js" +import { createSession, suggestBranch } from "./api" +import { useT } from "./i18n" +import { buildSessionKey } from "./setup-form-utils" -const GIT_URL_PATTERN = /^https?:\/\/.+\/.+/; +export { buildSessionKey } -export function SetupForm(props: { - email: string; - onCreated: (hash: string, url: string) => void; -}) { - const [repoUrl, setRepoUrl] = createSignal(""); - const [branch, setBranch] = createSignal(""); - const [error, setError] = createSignal(""); - const [submitting, setSubmitting] = createSignal(false); +const GIT_URL_PATTERN = /^https?:\/\/.+\/.+/ - const validate = (): string | null => { - const url = repoUrl().trim(); - if (!url) return "Repository URL is required"; - if (!GIT_URL_PATTERN.test(url)) return "Enter a valid HTTP(S) repository URL"; - if (!branch().trim()) return "Branch name is required"; - return null; - }; +export function SetupForm(props: { email: string; onCreated: (hash: string, url: string) => void }) { + const t = useT(useI18n()) + const [repoUrl, setRepoUrl] = createSignal("") + const [sourceBranch, setSourceBranch] = createSignal("") + const [sessionBranch, setSessionBranch] = createSignal("") + const [error, setError] = createSignal("") + const [submitting, setSubmitting] = createSignal(false) - const handleSubmit = async (e: SubmitEvent) => { - e.preventDefault(); - const validationError = validate(); - if (validationError) { - setError(validationError); - return; + // Fetch a suggested session branch name on mount + onMount(async () => { + try { + const { branch } = await suggestBranch("") + setSessionBranch(branch) + } catch { + // silent — will retry when URL is entered + } + }) + + const refreshSessionBranch = async (url: string) => { + if (!GIT_URL_PATTERN.test(url.trim())) return + try { + const { branch } = await suggestBranch(url.trim()) + setSessionBranch(branch) + } catch { + // silent } + } - setError(""); - setSubmitting(true); + const handleSubmit = async (e: SubmitEvent) => { + e.preventDefault() + const validated = buildSessionKey(repoUrl(), sourceBranch(), { + repoUrlRequired: t("form.error.repoUrl.required"), + repoUrlInvalid: t("form.error.repoUrl.invalid"), + sourceBranchRequired: t("form.error.sourceBranch.required"), + }) + if (!validated.valid) { + setError(validated.error) + return + } + if (!sessionBranch()) { + setError(t("form.error.sessionBranch")) + return + } + setError("") + setSubmitting(true) try { - const result = await createSession(repoUrl().trim(), branch().trim()); - props.onCreated(result.hash, result.url); + const result = await createSession(validated.repoUrl, sessionBranch(), validated.sourceBranch) + props.onCreated(result.hash, result.url) } catch (err) { - setError(err instanceof Error ? err.message : "Network error"); + setError(err instanceof Error ? err.message : "Network error") } finally { - setSubmitting(false); + setSubmitting(false) } - }; + } return (
@@ -50,21 +73,43 @@ export function SetupForm(props: {

+
+ refreshSessionBranch((e.currentTarget.querySelector("input") as HTMLInputElement)?.value ?? repoUrl()) + } + > + +
+ - + +
+

+ {t("form.sessionBranch.label")} +

+

+ {sessionBranch()} +

+
+
- ); + ) } diff --git a/packages/opencode-router-app/tsconfig.json b/packages/opencode-router-app/tsconfig.json index ec302b5df958..a20ad66b780a 100644 --- a/packages/opencode-router-app/tsconfig.json +++ b/packages/opencode-router-app/tsconfig.json @@ -11,7 +11,7 @@ "noEmit": true, "isolatedModules": true, "resolveJsonModule": true, - "types": ["vite/client"], + "types": ["vite/client", "bun-types"], "paths": { "@/*": ["./src/*"] } From 039bf74ab5971b9bd78856629a088a5b4618efe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Mon, 13 Apr 2026 09:33:45 +0200 Subject: [PATCH 005/616] =?UTF-8?q?feat(router-app):=20i18n=20=E2=80=94=20?= =?UTF-8?q?English=20and=20German=20translations=20for=20all=20UI=20string?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All user-visible strings extracted to typed dicts (en/de). createI18n(locale) resolves by exact match then language prefix (e.g. de-AT → de → en fallback). Wired via I18nProvider in entry.tsx using navigator.language; components use useI18n() + useT(). --- packages/opencode-router-app/src/i18n/de.ts | 39 +++++++++++++++++++ packages/opencode-router-app/src/i18n/en.ts | 38 ++++++++++++++++++ .../opencode-router-app/src/i18n/index.ts | 30 ++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 packages/opencode-router-app/src/i18n/de.ts create mode 100644 packages/opencode-router-app/src/i18n/en.ts create mode 100644 packages/opencode-router-app/src/i18n/index.ts diff --git a/packages/opencode-router-app/src/i18n/de.ts b/packages/opencode-router-app/src/i18n/de.ts new file mode 100644 index 000000000000..42ac1bdd5534 --- /dev/null +++ b/packages/opencode-router-app/src/i18n/de.ts @@ -0,0 +1,39 @@ +export const dict = { + "app.loading": "Lädt...", + "app.signedInAs": "Angemeldet als {{email}}", + "app.yourSessions": "Deine Sitzungen", + "app.newSession": "Neue Sitzung", + "app.back": "← Zurück", + "app.error.connect": "Verbindung fehlgeschlagen", + + "session.state.creating": "wird erstellt", + "session.state.running": "läuft", + "session.idle.stopsIn": "stoppt in ~{{minutes}}m", + "session.idle.stoppedOn": "gestoppt am {{date}}", + "session.idle.stoppingSoon": "stoppt gleich", + + "session.action.cancel": "Abbrechen", + "session.action.resume": "Fortsetzen", + "session.action.terminate": "Beenden", + "session.action.terminating": "Wird beendet…", + "session.terminate.title": "Sitzung beenden?", + "session.terminate.description": + '„{{repo}}" wird unwiderruflich gelöscht – alle nicht gespeicherten Änderungen gehen verloren.', + + "form.repoUrl.label": "Git-Repository-URL", + "form.repoUrl.placeholder": "https://github.com/org/repo.git", + "form.sourceBranch.label": "Quell-Branch (Startpunkt)", + "form.sourceBranch.placeholder": "main", + "form.sessionBranch.label": "Dein Sitzungs-Branch", + "form.submit": "Sitzung starten", + "form.submitting": "Wird gestartet...", + "form.error.sessionBranch": "Sitzungs-Branch konnte nicht generiert werden – bitte erneut versuchen", + "form.error.repoUrl.required": "Repository-URL ist erforderlich", + "form.error.repoUrl.invalid": "Gib eine gültige HTTP(S)-Repository-URL ein", + "form.error.sourceBranch.required": "Quell-Branch ist erforderlich", + + "loading.title": "Deine OpenCode-Sitzung wird gestartet...", + "loading.subtitle": "Das dauert normalerweise nur wenige Sekunden.", +} as const + +export type DictKey = keyof typeof dict diff --git a/packages/opencode-router-app/src/i18n/en.ts b/packages/opencode-router-app/src/i18n/en.ts new file mode 100644 index 000000000000..aee0271b29f1 --- /dev/null +++ b/packages/opencode-router-app/src/i18n/en.ts @@ -0,0 +1,38 @@ +export const dict = { + "app.loading": "Loading...", + "app.signedInAs": "Signed in as {{email}}", + "app.yourSessions": "Your sessions", + "app.newSession": "New Session", + "app.back": "← Back", + "app.error.connect": "Failed to connect", + + "session.state.creating": "creating", + "session.state.running": "running", + "session.idle.stopsIn": "stops in ~{{minutes}}m", + "session.idle.stoppedOn": "stopped on {{date}}", + "session.idle.stoppingSoon": "stopping soon", + + "session.action.cancel": "Cancel", + "session.action.resume": "Resume", + "session.action.terminate": "Terminate", + "session.action.terminating": "Terminating…", + "session.terminate.title": "Terminate session?", + "session.terminate.description": '"{{repo}}" will be permanently deleted — all uncommitted work will be lost.', + + "form.repoUrl.label": "Git repository URL", + "form.repoUrl.placeholder": "https://github.com/org/repo.git", + "form.sourceBranch.label": "Source branch (start from)", + "form.sourceBranch.placeholder": "main", + "form.sessionBranch.label": "Your session branch", + "form.submit": "Start Session", + "form.submitting": "Starting...", + "form.error.sessionBranch": "Session branch could not be generated — please try again", + "form.error.repoUrl.required": "Repository URL is required", + "form.error.repoUrl.invalid": "Enter a valid HTTP(S) repository URL", + "form.error.sourceBranch.required": "Source branch is required", + + "loading.title": "Starting your OpenCode session...", + "loading.subtitle": "This usually takes a few seconds.", +} as const + +export type DictKey = keyof typeof dict diff --git a/packages/opencode-router-app/src/i18n/index.ts b/packages/opencode-router-app/src/i18n/index.ts new file mode 100644 index 000000000000..923f333d8f8b --- /dev/null +++ b/packages/opencode-router-app/src/i18n/index.ts @@ -0,0 +1,30 @@ +import { dict as en } from "./en" +import { dict as de } from "./de" +import type { DictKey } from "./en" + +export type { DictKey } + +const dicts: Record> = { en, de } + +function resolve(locale: string): Record { + // exact match first (e.g. "de"), then language prefix (e.g. "de-AT" → "de") + return dicts[locale] ?? dicts[locale.split("-")[0]] ?? en +} + +function interpolate(value: string, params?: Record): string { + if (!params) return value + return value.replace(/{{\s*([^}]+?)\s*}}/g, (_, k) => String(params[String(k).trim()] ?? "")) +} + +export function createI18n(locale: string) { + const dict = resolve(locale) + // key is typed as string (not DictKey) so this object satisfies UiI18n from @opencode-ai/ui + const t = (key: string, params?: Record): string => + interpolate(dict[key] ?? (en as Record)[key] ?? key, params) + return { locale: () => locale, t } as const +} + +/** Typed helper for use inside this package — narrows key to known DictKey */ +export function useT(i18n: { t: (key: string, params?: Record) => string }) { + return (key: DictKey, params?: Record) => i18n.t(key, params) +} From 0536ea46074523e6b71f4968cd9851b56681047c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Mon, 13 Apr 2026 09:33:51 +0200 Subject: [PATCH 006/616] feat(deployment): add delete verb on PVCs to RBAC role The router needs to delete PVCs for terminateSession. Role in code namespace now grants get/list/create/delete on persistentvolumeclaims. --- deployment/homelab/src/index.ts | 93 +++++++++++++++------------------ 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/deployment/homelab/src/index.ts b/deployment/homelab/src/index.ts index 03a8eaad54bf..8ed6d62ff24a 100644 --- a/deployment/homelab/src/index.ts +++ b/deployment/homelab/src/index.ts @@ -1,57 +1,54 @@ -import * as pulumi from "@pulumi/pulumi"; -import * as k8s from "@pulumi/kubernetes"; -import { - AuthType, - createHomelabContextFromStack, -} from "@mrsimpson/homelab-core-components"; +import * as pulumi from "@pulumi/pulumi" +import * as k8s from "@pulumi/kubernetes" +import { AuthType, createHomelabContextFromStack } from "@mrsimpson/homelab-core-components" // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- -const APP_NAME = "code"; -const NAMESPACE = APP_NAME; -const ROUTER_PORT = 3000; -const OPENCODE_PORT = 4096; +const APP_NAME = "code" +const NAMESPACE = APP_NAME +const ROUTER_PORT = 3000 +const OPENCODE_PORT = 4096 /** Suffix appended to hash for session hostnames: -oc. */ -const ROUTE_SUFFIX = "-oc"; +const ROUTE_SUFFIX = "-oc" /** In-cluster URL the Cloudflare operator routes session traffic to */ -const ROUTER_SERVICE_URL = `http://${APP_NAME}.${NAMESPACE}.svc.cluster.local:80`; -const CF_OPERATOR_CONTAINER_NAME = "cloudflare-operator"; +const ROUTER_SERVICE_URL = `http://${APP_NAME}.${NAMESPACE}.svc.cluster.local:80` +const CF_OPERATOR_CONTAINER_NAME = "cloudflare-operator" // --------------------------------------------------------------------------- // Config // --------------------------------------------------------------------------- -const cfg = new pulumi.Config("code"); -const cloudflareConfig = new pulumi.Config("cloudflare"); +const cfg = new pulumi.Config("code") +const cloudflareConfig = new pulumi.Config("cloudflare") // StackReference to the homelab base stack — provides tunnelCname, cloudflareZoneId, domain -const homelabStackName = cfg.get("homelabStack") ?? "mrsimpson/homelab/dev"; -const homelabStack = new pulumi.StackReference(homelabStackName); +const homelabStackName = cfg.get("homelabStack") ?? "mrsimpson/homelab/dev" +const homelabStack = new pulumi.StackReference(homelabStackName) // Read infrastructure facts from homelab stack outputs -const domain = homelabStack.getOutput("domain") as pulumi.Output; -const cfZoneId = homelabStack.getOutput("cloudflareZoneId") as pulumi.Output; +const domain = homelabStack.getOutput("domain") as pulumi.Output +const cfZoneId = homelabStack.getOutput("cloudflareZoneId") as pulumi.Output // Tunnel ID — needed by the Cloudflare operator sidecar. // Exported from homelab stack as "tunnelId". -const cfTunnelId = homelabStack.getOutput("tunnelId") as pulumi.Output; +const cfTunnelId = homelabStack.getOutput("tunnelId") as pulumi.Output // Build HomelabContext from StackReference — homelab defaults apply -const homelab = createHomelabContextFromStack(homelabStack); +const homelab = createHomelabContextFromStack(homelabStack) // --------------------------------------------------------------------------- // App config // --------------------------------------------------------------------------- -const routerImage = cfg.require("routerImage"); -const cfOperatorImage = cfg.require("cfOperatorImage"); -const opencodeImage = cfg.require("opencodeImage"); -const anthropicApiKey = cfg.requireSecret("anthropicApiKey"); -const defaultGitRepo = cfg.get("defaultGitRepo"); -const storageSize = cfg.get("storageSize") ?? "2Gi"; -const cfApiToken = cloudflareConfig.requireSecret("apiToken"); +const routerImage = cfg.require("routerImage") +const cfOperatorImage = cfg.require("cfOperatorImage") +const opencodeImage = cfg.require("opencodeImage") +const anthropicApiKey = cfg.requireSecret("anthropicApiKey") +const defaultGitRepo = cfg.get("defaultGitRepo") +const storageSize = cfg.get("storageSize") ?? "2Gi" +const cfApiToken = cloudflareConfig.requireSecret("apiToken") // --------------------------------------------------------------------------- // 1. Namespace (pre-created; passed to ExposedWebApp so it doesn't re-create) @@ -68,7 +65,7 @@ const ns = new k8s.core.v1.Namespace(`${APP_NAME}-ns`, { "pod-security.kubernetes.io/warn-version": "latest", }, }, -}); +}) // --------------------------------------------------------------------------- // 2. RBAC — router manages user pods/PVCs; operator sidecar watches pods and @@ -85,7 +82,7 @@ const serviceAccount = new k8s.core.v1.ServiceAccount( }, }, { dependsOn: [ns] }, -); +) const role = new k8s.rbac.v1.Role( `${APP_NAME}-role`, @@ -104,7 +101,7 @@ const role = new k8s.rbac.v1.Role( { apiGroups: [""], resources: ["persistentvolumeclaims"], - verbs: ["get", "list", "create"], + verbs: ["get", "list", "create", "delete"], }, { apiGroups: ["traefik.io"], @@ -114,7 +111,7 @@ const role = new k8s.rbac.v1.Role( ], }, { dependsOn: [ns] }, -); +) const roleBinding = new k8s.rbac.v1.RoleBinding( `${APP_NAME}-rolebinding`, @@ -138,7 +135,7 @@ const roleBinding = new k8s.rbac.v1.RoleBinding( ], }, { dependsOn: [role, serviceAccount] }, -); +) // --------------------------------------------------------------------------- // 3. Secret — Anthropic API key mounted into session pods @@ -158,7 +155,7 @@ const apiKeysSecret = new k8s.core.v1.Secret( }, }, { dependsOn: [ns] }, -); +) // --------------------------------------------------------------------------- // 4. ConfigMap — opencode.json for session pods @@ -184,7 +181,7 @@ const configMap = new k8s.core.v1.ConfigMap( }, }, { dependsOn: [ns] }, -); +) // --------------------------------------------------------------------------- // 5. ExternalSecret — GHCR pull secret @@ -233,7 +230,7 @@ const pullSecret = new k8s.apiextensions.CustomResource( }, }, { dependsOn: [ns] }, -); +) // --------------------------------------------------------------------------- // 6. Secret — Cloudflare credentials for the operator sidecar @@ -253,7 +250,7 @@ const cfSecret = new k8s.core.v1.Secret( }, }, { dependsOn: [ns] }, -); +) // --------------------------------------------------------------------------- // 7. Cloudflare operator sidecar container spec @@ -309,13 +306,13 @@ const operatorSidecar = [ limits: { cpu: "200m", memory: "128Mi" }, }, }, -]; +] // --------------------------------------------------------------------------- // 8. ExposedWebApp — Deployment, Service, OAuth2-Proxy auth, main DNS CNAME // --------------------------------------------------------------------------- -const appDomain = pulumi.interpolate`${APP_NAME}.${domain}`; +const appDomain = pulumi.interpolate`${APP_NAME}.${domain}` export const app = homelab.createExposedWebApp( APP_NAME, @@ -349,18 +346,14 @@ export const app = homelab.createExposedWebApp( { name: "IMAGE_PULL_SECRET_NAME", value: "ghcr-pull-secret" }, { name: "ROUTER_DOMAIN", value: domain }, { name: "ROUTE_SUFFIX", value: ROUTE_SUFFIX }, - ...(defaultGitRepo - ? [{ name: "DEFAULT_GIT_REPO", value: defaultGitRepo }] - : []), + ...(defaultGitRepo ? [{ name: "DEFAULT_GIT_REPO", value: defaultGitRepo }] : []), ], probes: { readinessProbe: { httpGet: { path: "/api/sessions", port: ROUTER_PORT, - httpHeaders: [ - { name: "X-Auth-Request-Email", value: "healthcheck@probe" }, - ], + httpHeaders: [{ name: "X-Auth-Request-Email", value: "healthcheck@probe" }], }, initialDelaySeconds: 5, periodSeconds: 10, @@ -370,9 +363,7 @@ export const app = homelab.createExposedWebApp( httpGet: { path: "/api/sessions", port: ROUTER_PORT, - httpHeaders: [ - { name: "X-Auth-Request-Email", value: "healthcheck@probe" }, - ], + httpHeaders: [{ name: "X-Auth-Request-Email", value: "healthcheck@probe" }], }, initialDelaySeconds: 15, periodSeconds: 30, @@ -385,11 +376,11 @@ export const app = homelab.createExposedWebApp( { dependsOn: [roleBinding, pullSecret, cfSecret, apiKeysSecret, configMap], }, -); +) // --------------------------------------------------------------------------- // Stack outputs // --------------------------------------------------------------------------- -export const url = pulumi.interpolate`https://${appDomain}`; -export const namespace = app.namespace.metadata.name; +export const url = pulumi.interpolate`https://${appDomain}` +export const namespace = app.namespace.metadata.name From f2b82170b895ad8cbf8a70b2af2d2a778441dca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Mon, 13 Apr 2026 09:34:02 +0200 Subject: [PATCH 007/616] test: unit tests for router + router-app, Playwright E2E suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 35 unit tests for opencode-router (pod-manager, api, config) - 19 unit tests for opencode-router-app (session-utils, setup-form-utils, api client) - Playwright E2E suite (9 tests) targeting localhost:3002 with msedge channel: sessions list, new session form, suggestBranch, create→LoadingScreen→redirect, resume→LoadingScreen, terminate with confirm dialog --- .../e2e/session-lifecycle.spec.ts | 168 +++++++ .../opencode-router-app/playwright.config.ts | 36 ++ packages/opencode-router-app/src/api.test.ts | 89 ++++ packages/opencode-router-app/src/app.test.ts | 54 +++ .../src/setup-form.test.ts | 46 ++ .../opencode-router-app/src/test-setup.ts | 33 ++ packages/opencode-router/src/api.test.ts | 303 ++++++++++++ packages/opencode-router/src/config.test.ts | 15 + .../opencode-router/src/pod-manager.test.ts | 443 ++++++++++++++++++ 9 files changed, 1187 insertions(+) create mode 100644 packages/opencode-router-app/e2e/session-lifecycle.spec.ts create mode 100644 packages/opencode-router-app/playwright.config.ts create mode 100644 packages/opencode-router-app/src/api.test.ts create mode 100644 packages/opencode-router-app/src/app.test.ts create mode 100644 packages/opencode-router-app/src/setup-form.test.ts create mode 100644 packages/opencode-router-app/src/test-setup.ts create mode 100644 packages/opencode-router/src/api.test.ts create mode 100644 packages/opencode-router/src/config.test.ts create mode 100644 packages/opencode-router/src/pod-manager.test.ts diff --git a/packages/opencode-router-app/e2e/session-lifecycle.spec.ts b/packages/opencode-router-app/e2e/session-lifecycle.spec.ts new file mode 100644 index 000000000000..2fd71fb98e81 --- /dev/null +++ b/packages/opencode-router-app/e2e/session-lifecycle.spec.ts @@ -0,0 +1,168 @@ +import { test, expect, type Page } from "@playwright/test" + +/** + * End-to-end tests for the opencode-router session lifecycle: + * 1. Sessions list — shows existing sessions with correct state badges + * 2. New session — form with repo URL, source branch, suggested session branch + * 3. Create session — submits, shows loading screen, auto-redirects to session URL + * 4. Resume session — stopped session can be resumed → transitions to creating → running + * 5. Terminate session — session disappears from list after termination + * + * Requires: + * - opencode-router running at http://localhost:3002 with DEV_EMAIL=dev@local.test + * - opencode-router-app Vite dev server at http://localhost:5173 (proxied by router) + * - kubectl access to the "code" namespace + */ + +const REPO_URL = "https://github.com/mrsimpson/port-a-dice" +const SOURCE_BRANCH = "main" + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +async function goHome(page: Page) { + await page.goto("/") + await expect(page.getByText("Signed in as")).toBeVisible() +} + +async function openNewSessionForm(page: Page) { + await page.getByRole("button", { name: "New Session" }).click() + await expect(page.getByRole("textbox", { name: "Git repository URL" })).toBeVisible() +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +test.describe("sessions list", () => { + test("shows email and session list on load", async ({ page }) => { + await goHome(page) + await expect(page.getByText("Signed in as dev@local.test")).toBeVisible() + await expect(page.getByText("Your sessions")).toBeVisible() + }) + + test("shows New Session button", async ({ page }) => { + await goHome(page) + await expect(page.getByRole("button", { name: "New Session" })).toBeVisible() + }) +}) + +test.describe("new session form", () => { + test("opens setup form on New Session click", async ({ page }) => { + await goHome(page) + await openNewSessionForm(page) + await expect(page.getByRole("textbox", { name: "Git repository URL" })).toBeVisible() + await expect(page.getByRole("textbox", { name: "Source branch (start from)" })).toBeVisible() + await expect(page.getByRole("button", { name: "Start Session" })).toBeVisible() + }) + + test("Back button returns to sessions list", async ({ page }) => { + await goHome(page) + await openNewSessionForm(page) + await page.getByRole("button", { name: "← Back" }).click() + await expect(page.getByText("Your sessions")).toBeVisible() + }) + + test("suggests session branch after repo URL entry", async ({ page }) => { + await goHome(page) + await openNewSessionForm(page) + + await page.getByRole("textbox", { name: "Git repository URL" }).fill(REPO_URL) + await page.keyboard.press("Tab") // trigger blur → suggestBranch call + + // Wait for the suggested branch label to appear + await expect(page.getByText("Your session branch")).toBeVisible() + const branch = page.locator("p", { hasText: /^[a-z]+-[a-z]+-[a-z]+$/ }) + await expect(branch).toBeVisible() + }) + + test("shows validation error when source branch is empty", async ({ page }) => { + await goHome(page) + await openNewSessionForm(page) + + await page.getByRole("textbox", { name: "Git repository URL" }).fill(REPO_URL) + await page.keyboard.press("Tab") + await expect(page.getByText("Your session branch")).toBeVisible() + + // Submit without filling source branch + await page.getByRole("button", { name: "Start Session" }).click() + await expect(page.getByText("Source branch is required")).toBeVisible() + }) +}) + +test.describe("create session", () => { + test("creates session, shows loading screen, auto-redirects to session URL", async ({ page }) => { + await goHome(page) + await openNewSessionForm(page) + + await page.getByRole("textbox", { name: "Git repository URL" }).fill(REPO_URL) + await page.keyboard.press("Tab") + await expect(page.getByText("Your session branch")).toBeVisible() + + await page.getByRole("textbox", { name: "Source branch (start from)" }).fill(SOURCE_BRANCH) + await page.getByRole("button", { name: "Start Session" }).click() + + // Loading screen + await expect(page.getByText("Starting your OpenCode session...")).toBeVisible() + await expect(page.getByText("This usually takes a few seconds.")).toBeVisible() + + // Wait for auto-redirect to session subdomain (pod starts up + git-init) + await page.waitForURL(/\.localhost:3002\/$/, { timeout: 90_000 }) + + // Verify we're on the opencode app (not the router SPA) + const url = page.url() + expect(url).toMatch(/^http:\/\/[a-f0-9]{12}\.localhost:3002\/$/) + }) +}) + +test.describe("resume session", () => { + test("resumes a stopped session and shows it as creating", async ({ page }) => { + await goHome(page) + + // Find a stopped session + const stoppedCard = page.locator("text=stopped").first() + const hasStoppedSession = (await stoppedCard.count()) > 0 + + if (!hasStoppedSession) { + test.skip() + return + } + + // Click Resume on the first stopped session + const resumeBtn = page.getByRole("button", { name: "Resume" }).first() + await resumeBtn.click() + + // After resume, the session should transition to creating (and become a link) + await expect(page.getByText("creating")).toBeVisible({ timeout: 5_000 }) + }) +}) + +test.describe("terminate session", () => { + test("removes session from list after termination", async ({ page }) => { + await goHome(page) + + // Count sessions before + const before = await page.getByRole("button", { name: "Terminate" }).count() + if (before === 0) { + test.skip() + return + } + + // Note the session branch name before terminating + const firstCard = page.locator('[class*="rounded-lg"]').first() + const branchText = await firstCard.locator("p").nth(1).textContent() + + await page.getByRole("button", { name: "Terminate" }).first().click() + + // Session should disappear from list + const after = await page.getByRole("button", { name: "Terminate" }).count() + expect(after).toBe(before - 1) + + // The specific branch should no longer appear + if (branchText) { + const branchName = branchText.split(" · ")[0] + await expect(page.getByText(branchName, { exact: true })).not.toBeVisible() + } + }) +}) diff --git a/packages/opencode-router-app/playwright.config.ts b/packages/opencode-router-app/playwright.config.ts new file mode 100644 index 000000000000..6e425d08c606 --- /dev/null +++ b/packages/opencode-router-app/playwright.config.ts @@ -0,0 +1,36 @@ +import { defineConfig, devices } from "@playwright/test" + +/** + * E2E tests for opencode-router-app. + * + * Prerequisites (already running): + * - opencode-router: bun dev (packages/opencode-router) → http://localhost:3002 + * - opencode-router-app: bun dev (packages/opencode-router-app) → http://localhost:5173 (proxied via router) + * + * The tests target the router at http://localhost:3002. + * DEV_EMAIL=dev@local.test is set in the router's .env.local so no real auth is needed. + * + * Run: + * cd packages/opencode-router-app && bunx playwright test + */ +export default defineConfig({ + testDir: "./e2e", + outputDir: "./e2e/test-results", + timeout: 120_000, + expect: { timeout: 15_000 }, + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + reporter: [["html", { outputFolder: "e2e/playwright-report", open: "never" }], ["line"]], + use: { + baseURL: process.env.ROUTER_URL ?? "http://localhost:3002", + trace: "on-first-retry", + screenshot: "only-on-failure", + }, + projects: [ + { + name: "msedge", + use: { ...devices["Desktop Edge"], channel: "msedge" }, + }, + ], +}) diff --git a/packages/opencode-router-app/src/api.test.ts b/packages/opencode-router-app/src/api.test.ts new file mode 100644 index 000000000000..2bacc43ca668 --- /dev/null +++ b/packages/opencode-router-app/src/api.test.ts @@ -0,0 +1,89 @@ +import { describe, it, expect, mock, beforeEach } from "bun:test" +import { terminateSession, resumeSession, suggestBranch, type Session } from "./api" + +const fetchMock = mock(() => Promise.resolve({ ok: true, json: () => Promise.resolve({}) })) +global.fetch = fetchMock as any + +beforeEach(() => { + fetchMock.mockClear() +}) + +describe("terminateSession", () => { + it("sends DELETE to /api/sessions/:hash", async () => { + await terminateSession("abc123456789") + expect(fetchMock).toHaveBeenCalledWith("/api/sessions/abc123456789", { + method: "DELETE", + }) + }) + + it("throws on non-ok response", async () => { + fetchMock.mockImplementationOnce(() => Promise.resolve({ ok: false, status: 404, json: () => Promise.resolve({}) })) + await expect(terminateSession("abc123456789")).rejects.toThrow() + }) +}) + +describe("resumeSession", () => { + it("sends POST to /api/sessions/:hash/resume", async () => { + await resumeSession("abc123456789") + expect(fetchMock).toHaveBeenCalledWith("/api/sessions/abc123456789/resume", { + method: "POST", + }) + }) + + it("throws on non-ok response", async () => { + fetchMock.mockImplementationOnce(() => Promise.resolve({ ok: false, status: 500, json: () => Promise.resolve({}) })) + await expect(resumeSession("abc123456789")).rejects.toThrow() + }) +}) + +describe("suggestBranch", () => { + it("sends GET to /api/sessions/suggest-branch?repoUrl=... and returns branch", async () => { + fetchMock.mockImplementationOnce(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({ branch: "feature/my-branch" }), + }), + ) + const result = await suggestBranch("https://github.com/org/repo") + expect(fetchMock).toHaveBeenCalledWith("/api/sessions/suggest-branch?repoUrl=https%3A%2F%2Fgithub.com%2Forg%2Frepo") + expect(result).toEqual({ branch: "feature/my-branch" }) + }) + + it("throws on non-ok response", async () => { + fetchMock.mockImplementationOnce(() => Promise.resolve({ ok: false, status: 400, json: () => Promise.resolve({}) })) + await expect(suggestBranch("https://github.com/org/repo")).rejects.toThrow() + }) +}) + +describe("Session type", () => { + it("accepts stopped as a valid state (compile-time check via assignment)", () => { + const s: Session = { + hash: "abc", + email: "u@example.com", + repoUrl: "https://github.com/org/repo", + branch: "calm-snails-dream", + sourceBranch: "main", + state: "stopped", + url: "https://example.com", + lastActivity: "2026-04-13T00:00:00Z", + idleTimeoutMinutes: 30, + } + expect(s.state).toBe("stopped") + }) + + it("has lastActivity and idleTimeoutMinutes fields", () => { + const s: Session = { + hash: "xyz", + email: "u@example.com", + repoUrl: "https://github.com/org/repo", + branch: "brave-tigers-run", + sourceBranch: "dev", + state: "running", + url: "https://example.com", + lastActivity: "2026-04-13T12:00:00Z", + idleTimeoutMinutes: 60, + } + expect(typeof s.lastActivity).toBe("string") + expect(typeof s.idleTimeoutMinutes).toBe("number") + }) +}) diff --git a/packages/opencode-router-app/src/app.test.ts b/packages/opencode-router-app/src/app.test.ts new file mode 100644 index 000000000000..75eb261ded3f --- /dev/null +++ b/packages/opencode-router-app/src/app.test.ts @@ -0,0 +1,54 @@ +import { describe, it, expect } from "bun:test" +import { computeIdleStatus, type IdleLabels } from "./session-utils" + +const labels: IdleLabels = { + stopsIn: (m) => `stops in ~${m}m`, + stoppedOn: (d) => `stopped on ${d}`, + stoppingSoon: "stopping soon", +} + +describe("computeIdleStatus", () => { + it("returns 'stops in ~Xm' for a running session active recently", () => { + const now = new Date() + const twoMinsAgo = new Date(now.getTime() - 2 * 60_000).toISOString() + const result = computeIdleStatus("running", twoMinsAgo, 15, labels) + expect(result.label).toBe("stops in ~13m") + expect(result.stopsInMinutes).toBe(13) + }) + + it("returns 'stopping soon' for a running session past timeout", () => { + const now = new Date() + const twentyMinsAgo = new Date(now.getTime() - 20 * 60_000).toISOString() + const result = computeIdleStatus("running", twentyMinsAgo, 15, labels) + expect(result.label).toBe("stopping soon") + }) + + it("handles zero minutes since activity (just active)", () => { + const result = computeIdleStatus("running", new Date().toISOString(), 15, labels) + expect(result.stopsInMinutes).toBe(15) + expect(result.label).toBe("stops in ~15m") + }) + + it("handles exactly at timeout boundary", () => { + const now = new Date() + const exactTimeout = new Date(now.getTime() - 15 * 60_000).toISOString() + const result = computeIdleStatus("running", exactTimeout, 15, labels) + // At exactly 15min: stopsInMinutes = 0, label "stops in ~0m" + expect(result.stopsInMinutes).toBe(0) + }) + + it("returns 'stopped on ' for a stopped session regardless of timeout", () => { + const past = new Date("2026-03-15T10:00:00Z").toISOString() + const result = computeIdleStatus("stopped", past, 15, labels) + expect(result.label).toMatch(/^stopped on /) + expect(result.stopsInMinutes).toBeNull() + expect(result.stoppedMinutesAgo).toBeNull() + }) + + it("creating state shows stops countdown same as running", () => { + const now = new Date() + const fiveMinsAgo = new Date(now.getTime() - 5 * 60_000).toISOString() + const result = computeIdleStatus("creating", fiveMinsAgo, 15, labels) + expect(result.stopsInMinutes).toBe(10) + }) +}) diff --git a/packages/opencode-router-app/src/setup-form.test.ts b/packages/opencode-router-app/src/setup-form.test.ts new file mode 100644 index 000000000000..fb45382c08a3 --- /dev/null +++ b/packages/opencode-router-app/src/setup-form.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect } from "bun:test" +import { buildSessionKey, type ValidationErrors } from "./setup-form-utils" + +const errors: ValidationErrors = { + repoUrlRequired: "Repository URL is required", + repoUrlInvalid: "Enter a valid HTTP(S) repository URL", + sourceBranchRequired: "Source branch is required", +} + +describe("buildSessionKey", () => { + it("returns valid with sourceBranch for a good URL and source branch", () => { + const result = buildSessionKey("https://github.com/org/repo.git", "main", errors) + expect(result.valid).toBe(true) + if (result.valid) { + expect(result.repoUrl).toBe("https://github.com/org/repo.git") + expect(result.sourceBranch).toBe("main") + } + }) + + it("trims whitespace from inputs", () => { + const result = buildSessionKey(" https://github.com/org/repo ", " main ", errors) + expect(result.valid).toBe(true) + if (result.valid) { + expect(result.repoUrl).toBe("https://github.com/org/repo") + expect(result.sourceBranch).toBe("main") + } + }) + + it("returns error when repoUrl is empty", () => { + const result = buildSessionKey("", "main", errors) + expect(result.valid).toBe(false) + if (!result.valid) expect(result.error).toContain("required") + }) + + it("returns error for invalid URL format", () => { + const result = buildSessionKey("not-a-url", "main", errors) + expect(result.valid).toBe(false) + if (!result.valid) expect(result.error).toContain("valid") + }) + + it("returns error when source branch is empty", () => { + const result = buildSessionKey("https://github.com/org/repo.git", "", errors) + expect(result.valid).toBe(false) + if (!result.valid) expect(result.error).toContain("required") + }) +}) diff --git a/packages/opencode-router-app/src/test-setup.ts b/packages/opencode-router-app/src/test-setup.ts new file mode 100644 index 000000000000..65b2419938cf --- /dev/null +++ b/packages/opencode-router-app/src/test-setup.ts @@ -0,0 +1,33 @@ +// Preload: stub solid-js/web with browser-compatible no-ops so tests that +// only exercise pure logic (like buildSessionKey) can import .tsx files without +// triggering the "Client-only API called on the server side" error. +import { mock } from "bun:test" + +// Stub the functions that @kobalte/core calls at module-evaluation time. +mock.module("solid-js/web", () => { + const noop = () => {} + const noopReturn = () => ({}) + return { + template: () => ({}), + createComponent: noopReturn, + effect: noop, + setAttribute: noop, + mergeProps: (...args: object[]) => Object.assign({}, ...args), + spread: noop, + classList: noop, + style: noop, + insert: noop, + assign: noop, + addEventListener: noop, + delegateEvents: noop, + dynamicProperty: noop, + getNextElement: noop, + hydrate: noop, + render: noop, + isServer: false, + isDev: false, + // pass-through for SSR helpers + ssr: (...args: unknown[]) => args, + escape: (v: unknown) => v, + } +}) diff --git a/packages/opencode-router/src/api.test.ts b/packages/opencode-router/src/api.test.ts new file mode 100644 index 000000000000..3f02fc98353d --- /dev/null +++ b/packages/opencode-router/src/api.test.ts @@ -0,0 +1,303 @@ +import { mock, describe, it, expect, beforeEach } from "bun:test" +import { Readable } from "node:stream" +import type http from "node:http" + +process.env.OPENCODE_IMAGE = "test" +process.env.ROUTER_DOMAIN = "test.local" + +// --------------------------------------------------------------------------- +// Mock pod-manager BEFORE importing api.ts +// NOTE: "suggest-branch" route MUST be matched before /:hash route in api.ts, +// otherwise GET /api/sessions/suggest-branch would be misinterpreted as a +// request for the session with hash "suggest-branch" (which is not a 12-char +// hex string, but guarding with regex alone may be fragile). The implementation +// should place the suggest-branch route before the /:hash pattern. +// --------------------------------------------------------------------------- + +const mocks = { + listUserSessions: mock((): Promise => Promise.resolve([])), + ensurePVC: mock(() => Promise.resolve()), + ensurePod: mock(() => Promise.resolve("abc123")), + getPodState: mock(() => Promise.resolve("running")), + getSessionHash: mock(() => "abc123456789"), + terminateSession: mock(() => Promise.resolve()), + resumeSession: mock(() => Promise.resolve()), + suggestBranch: mock(() => Promise.resolve("calm-snails-dream")), +} + +mock.module("./pod-manager.js", () => mocks) + +const { handleApi } = await import("./api.js") + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function fakeReq(method: string, url: string, body?: object): http.IncomingMessage { + const r = new Readable() as any + r.method = method + r.url = url + r.headers = { "x-forwarded-proto": "https" } + if (body) { + r.push(JSON.stringify(body)) + r.push(null) + } else { + r.push(null) + } + return r +} + +function fakeRes(): { + statusCode: number + body: string + headers: Record + writeHead: Function + end: Function +} { + const r: any = { statusCode: 200, body: "", headers: {} } + r.writeHead = (status: number, headers?: object) => { + r.statusCode = status + Object.assign(r.headers, headers ?? {}) + return r + } + r.end = (data?: string) => { + r.body = data ?? "" + } + return r +} + +const EMAIL = "user@example.com" + +beforeEach(() => { + mocks.listUserSessions.mockReset() + mocks.listUserSessions.mockImplementation(() => Promise.resolve([])) + mocks.terminateSession.mockReset() + mocks.terminateSession.mockImplementation(() => Promise.resolve()) + mocks.resumeSession.mockReset() + mocks.resumeSession.mockImplementation(() => Promise.resolve()) + mocks.suggestBranch.mockReset() + mocks.suggestBranch.mockImplementation(() => Promise.resolve("calm-snails-dream")) + mocks.getPodState.mockReset() + mocks.getPodState.mockImplementation(() => Promise.resolve("running")) +}) + +// --------------------------------------------------------------------------- +// 1.3.6: DELETE /api/sessions/:hash +// --------------------------------------------------------------------------- + +describe("DELETE /api/sessions/:hash", () => { + it("returns 204 on successful termination", async () => { + const req = fakeReq("DELETE", "/api/sessions/abc123456789") + const res = fakeRes() + + const handled = await handleApi(req as any, res as any, EMAIL) + + expect(handled).toBe(true) + expect(res.statusCode).toBe(204) + expect(mocks.terminateSession).toHaveBeenCalledTimes(1) + }) + + it("returns 403 when terminateSession throws Forbidden", async () => { + mocks.terminateSession.mockImplementation(() => Promise.reject(new Error("Forbidden"))) + const req = fakeReq("DELETE", "/api/sessions/abc123456789") + const res = fakeRes() + + const handled = await handleApi(req as any, res as any, EMAIL) + + expect(handled).toBe(true) + expect(res.statusCode).toBe(403) + }) + + it("returns 404 when terminateSession throws NotFound", async () => { + mocks.terminateSession.mockImplementation(() => Promise.reject(new Error("NotFound"))) + const req = fakeReq("DELETE", "/api/sessions/abc123456789") + const res = fakeRes() + + const handled = await handleApi(req as any, res as any, EMAIL) + + expect(handled).toBe(true) + expect(res.statusCode).toBe(404) + }) + + it("returns false (not handled) for DELETE on /api/sessions (no hash)", async () => { + const req = fakeReq("DELETE", "/api/sessions") + const res = fakeRes() + + const handled = await handleApi(req as any, res as any, EMAIL) + + expect(handled).toBe(false) + }) +}) + +// --------------------------------------------------------------------------- +// 1.3.7: POST /api/sessions/:hash/resume +// --------------------------------------------------------------------------- + +describe("POST /api/sessions/:hash/resume", () => { + it("returns 200 with state creating after resuming", async () => { + const req = fakeReq("POST", "/api/sessions/abc123456789/resume") + const res = fakeRes() + + const handled = await handleApi(req as any, res as any, EMAIL) + + expect(handled).toBe(true) + expect(res.statusCode).toBe(200) + const body = JSON.parse(res.body) + expect(body.state).toBe("creating") + expect(mocks.resumeSession).toHaveBeenCalledTimes(1) + }) + + it("returns 403 when resumeSession throws Forbidden", async () => { + mocks.resumeSession.mockImplementation(() => Promise.reject(new Error("Forbidden"))) + const req = fakeReq("POST", "/api/sessions/abc123456789/resume") + const res = fakeRes() + + const handled = await handleApi(req as any, res as any, EMAIL) + + expect(handled).toBe(true) + expect(res.statusCode).toBe(403) + }) + + it("returns 404 when resumeSession throws NotFound", async () => { + mocks.resumeSession.mockImplementation(() => Promise.reject(new Error("NotFound"))) + const req = fakeReq("POST", "/api/sessions/abc123456789/resume") + const res = fakeRes() + + const handled = await handleApi(req as any, res as any, EMAIL) + + expect(handled).toBe(true) + expect(res.statusCode).toBe(404) + }) +}) + +// --------------------------------------------------------------------------- +// 1.3.8: GET /api/sessions/suggest-branch?repoUrl= +// --------------------------------------------------------------------------- + +describe("GET /api/sessions/suggest-branch", () => { + it("returns 200 with branch field when repoUrl is provided", async () => { + const req = fakeReq("GET", "/api/sessions/suggest-branch?repoUrl=https%3A%2F%2Fgithub.com%2Fx%2Fy") + const res = fakeRes() + + const handled = await handleApi(req as any, res as any, EMAIL) + + expect(handled).toBe(true) + expect(res.statusCode).toBe(200) + const body = JSON.parse(res.body) + expect(typeof body.branch).toBe("string") + expect(body.branch).toBe("calm-snails-dream") + expect(mocks.suggestBranch).toHaveBeenCalledTimes(1) + }) + + it("returns 400 when repoUrl is missing", async () => { + const req = fakeReq("GET", "/api/sessions/suggest-branch") + const res = fakeRes() + + const handled = await handleApi(req as any, res as any, EMAIL) + + expect(handled).toBe(true) + expect(res.statusCode).toBe(400) + const body = JSON.parse(res.body) + expect(typeof body.error).toBe("string") + }) +}) + +// --------------------------------------------------------------------------- +// 1.3.9: lastActivity + idleTimeoutMinutes in session list and get +// --------------------------------------------------------------------------- + +describe("GET /api/sessions (list) includes lastActivity", () => { + it("session list response contains lastActivity and idleTimeoutMinutes per session", async () => { + const sessions: object[] = [ + { + hash: "abc123456789", + email: EMAIL, + repoUrl: "https://github.com/x/y", + branch: "main", + state: "running", + url: "https://abc123456789.opencode.test.local", + lastActivity: "2025-06-01T12:00:00Z", + idleTimeoutMinutes: 30, + }, + ] + mocks.listUserSessions.mockImplementation(() => Promise.resolve(sessions)) + + const req = fakeReq("GET", "/api/sessions") + const res = fakeRes() + + const handled = await handleApi(req as any, res as any, EMAIL) + + expect(handled).toBe(true) + expect(res.statusCode).toBe(200) + const body = JSON.parse(res.body) + expect(body.sessions).toHaveLength(1) + expect(typeof body.sessions[0].lastActivity).toBe("string") + expect(typeof body.sessions[0].idleTimeoutMinutes).toBe("number") + }) +}) + +describe("GET /api/sessions/:hash includes lastActivity", () => { + it("single session response includes lastActivity and idleTimeoutMinutes", async () => { + // Current implementation calls getPodState which only returns state — + // the new implementation must call getSessionState or a richer lookup. + // This test will FAIL because the current GET /api/sessions/:hash returns + // { hash, state, url } with no lastActivity or idleTimeoutMinutes. + mocks.getPodState.mockImplementation(() => Promise.resolve("running")) + + const req = fakeReq("GET", "/api/sessions/abc123456789") + const res = fakeRes() + + const handled = await handleApi(req as any, res as any, EMAIL) + + expect(handled).toBe(true) + expect(res.statusCode).toBe(200) + const body = JSON.parse(res.body) + expect(body.hash).toBe("abc123456789") + // These assertions WILL FAIL until the implementation is updated: + expect(typeof body.lastActivity).toBe("string") + expect(typeof body.idleTimeoutMinutes).toBe("number") + }) +}) + +// --------------------------------------------------------------------------- +// 1.3.14: POST /api/sessions passes sourceBranch to ensurePod/ensurePVC +// --------------------------------------------------------------------------- + +describe("POST /api/sessions with sourceBranch", () => { + it("accepts sourceBranch in request body and passes it through", async () => { + mocks.ensurePVC.mockReset() + mocks.ensurePVC.mockImplementation(() => Promise.resolve()) + mocks.ensurePod.mockReset() + mocks.ensurePod.mockImplementation(() => Promise.resolve("abc123456789")) + + const req = fakeReq("POST", "/api/sessions", { + repoUrl: "https://github.com/x/y", + branch: "calm-snails-dream", + sourceBranch: "main", + }) + const res = fakeRes() + + const handled = await handleApi(req as any, res as any, EMAIL) + + expect(handled).toBe(true) + expect(res.statusCode).toBe(201) + // WILL FAIL until api.ts reads sourceBranch from body and passes to SessionKey + const sessionKeyPassed = (mocks.ensurePVC as any).mock.calls[0]?.[0] + expect(sessionKeyPassed?.sourceBranch).toBe("main") + }) + + it("returns 400 when sourceBranch is missing", async () => { + const req = fakeReq("POST", "/api/sessions", { + repoUrl: "https://github.com/x/y", + branch: "calm-snails-dream", + // sourceBranch intentionally omitted + }) + const res = fakeRes() + + const handled = await handleApi(req as any, res as any, EMAIL) + + expect(handled).toBe(true) + // WILL FAIL: current impl doesn't require sourceBranch + expect(res.statusCode).toBe(400) + }) +}) diff --git a/packages/opencode-router/src/config.test.ts b/packages/opencode-router/src/config.test.ts new file mode 100644 index 000000000000..75b85cf58bab --- /dev/null +++ b/packages/opencode-router/src/config.test.ts @@ -0,0 +1,15 @@ +import { describe, it, expect } from "bun:test" +import { spawnSync } from "node:child_process" + +describe("config defaults", () => { + it("idleTimeoutMinutes defaults to 15", () => { + const { IDLE_TIMEOUT_MINUTES: _, ...rest } = process.env + const env = { ...rest, OPENCODE_IMAGE: "test", ROUTER_DOMAIN: "test.local" } + const result = spawnSync( + process.execPath, + ["--eval", "import('./src/config.ts').then(m => process.stdout.write(String(m.config.idleTimeoutMinutes)))"], + { env, encoding: "utf-8", cwd: "/Users/oliverjaegle/projects/open-source/opencode/packages/opencode-router" }, + ) + expect(result.stdout.trim()).toBe("15") + }) +}) diff --git a/packages/opencode-router/src/pod-manager.test.ts b/packages/opencode-router/src/pod-manager.test.ts new file mode 100644 index 000000000000..a707e02140af --- /dev/null +++ b/packages/opencode-router/src/pod-manager.test.ts @@ -0,0 +1,443 @@ +import { mock, describe, it, expect, beforeEach } from "bun:test" + +// Set required env vars before config module is loaded +process.env.OPENCODE_IMAGE = "test" +process.env.ROUTER_DOMAIN = "test.local" + +// --- Fake k8s client state (mutated per test via helpers below) --- +let fakePVCs: object[] = [] +let fakePods: object[] = [] +let createPodCalls: object[] = [] + +const fakeK8sApi = { + listNamespacedPersistentVolumeClaim: async (_opts: object) => ({ items: fakePVCs }), + listNamespacedPod: async (_opts: object) => ({ items: fakePods }), + readNamespacedPod: async ({ name }: { name: string }) => { + const pod = (fakePods as any[]).find((p) => p.metadata?.name === name) + if (!pod) { + const err: any = new Error("not found") + err.code = 404 + throw err + } + return pod + }, + readNamespacedPersistentVolumeClaim: async ({ name }: { name: string }) => { + const pvc = (fakePVCs as any[]).find((p) => p.metadata?.name === name) + if (!pvc) { + const err: any = new Error("not found") + err.code = 404 + throw err + } + return pvc + }, + createNamespacedPod: async ({ namespace, body }: { namespace: string; body: object }) => { + createPodCalls.push({ namespace, body }) + fakePods = [...(fakePods as any[]), body] + return body + }, + deleteNamespacedPod: async ({ name }: { name: string }) => { + const idx = (fakePods as any[]).findIndex((p) => p.metadata?.name === name) + if (idx === -1) { + const err: any = new Error("not found") + err.code = 404 + throw err + } + fakePods = (fakePods as any[]).filter((_, i) => i !== idx) + }, + deleteNamespacedPersistentVolumeClaim: async ({ name }: { name: string }) => { + const idx = (fakePVCs as any[]).findIndex((p) => p.metadata?.name === name) + if (idx === -1) { + const err: any = new Error("not found") + err.code = 404 + throw err + } + fakePVCs = (fakePVCs as any[]).filter((_, i) => i !== idx) + }, +} + +// Mock @kubernetes/client-node before any import of pod-manager +mock.module("@kubernetes/client-node", () => { + return { + KubeConfig: class { + loadFromCluster() {} + loadFromDefault() {} + makeApiClient(_cls: unknown) { + return fakeK8sApi + } + }, + CoreV1Api: class {}, + } +}) + +// Also mock fs so the SA token path check doesn't matter +mock.module("node:fs", () => { + return { + default: { existsSync: (_path: string) => false }, + existsSync: (_path: string) => false, + } +}) + +// Dynamic import AFTER mocks are registered (bun evaluates mock.module synchronously) +const { listUserSessions, terminateSession, resumeSession, suggestBranch } = await import("./pod-manager.js") + +// --- Helpers --- + +function makePVC(sessionHash: string, email: string, repoUrl: string, branch: string, lastActivity?: string) { + return { + metadata: { + name: `opencode-pvc-${sessionHash}`, + namespace: "opencode", + creationTimestamp: "2025-01-01T00:00:00Z", + labels: { + "opencode.ai/session-hash": sessionHash, + "app.kubernetes.io/managed-by": "opencode-router", + }, + annotations: { + "opencode.ai/user-email": email, + "opencode.ai/repo-url": repoUrl, + "opencode.ai/branch": branch, + ...(lastActivity ? { "opencode.ai/last-activity": lastActivity } : {}), + }, + }, + spec: {}, + status: { phase: "Bound" }, + } +} + +function makeRunningPod(sessionHash: string, email: string, repoUrl: string, branch: string) { + return { + metadata: { + name: `opencode-session-${sessionHash}`, + namespace: "opencode", + labels: { + "opencode.ai/session-hash": sessionHash, + "app.kubernetes.io/managed-by": "opencode-router", + }, + annotations: { + "opencode.ai/last-activity": "2025-06-01T12:00:00Z", + "opencode.ai/user-email": email, + "opencode.ai/repo-url": repoUrl, + "opencode.ai/branch": branch, + }, + }, + status: { phase: "Running", podIP: "10.0.0.1" }, + } +} + +function makePendingPod(sessionHash: string, email: string, repoUrl: string, branch: string) { + return { + metadata: { + name: `opencode-session-${sessionHash}`, + namespace: "opencode", + labels: { + "opencode.ai/session-hash": sessionHash, + "app.kubernetes.io/managed-by": "opencode-router", + }, + annotations: { + "opencode.ai/last-activity": "2025-06-01T12:00:00Z", + "opencode.ai/user-email": email, + "opencode.ai/repo-url": repoUrl, + "opencode.ai/branch": branch, + }, + }, + status: { phase: "Pending" }, + } +} + +const fakeReq = { headers: { "x-forwarded-proto": "https" } } as any + +const EMAIL = "test@example.com" +const REPO = "https://github.com/x/y" +const BRANCH = "main" + +// Compute hash identically to pod-manager's getSessionHash +import crypto from "node:crypto" +function computeHash(email: string, repo: string, branch: string) { + const key = `${email.toLowerCase().trim()}:${repo.trim()}:${branch.trim()}` + return crypto.createHash("sha256").update(key).digest("hex").slice(0, 12) +} +const SESSION_HASH = computeHash(EMAIL, REPO, BRANCH) + +beforeEach(() => { + fakePVCs = [] + fakePods = [] + createPodCalls = [] +}) + +// --------------------------------------------------------------------------- + +describe("listUserSessions", () => { + it("returns stopped session when PVC exists but no pod", async () => { + fakePVCs = [makePVC(SESSION_HASH, EMAIL, REPO, BRANCH)] + fakePods = [] + + const result = await listUserSessions(EMAIL, fakeReq) + + expect(result).toHaveLength(1) + // Expect state to be "stopped" — WILL FAIL: current impl scans pods (empty), returns [] + expect((result[0] as any).state).toBe("stopped") + expect((result[0] as any).hash).toBe(SESSION_HASH) + expect((result[0] as any).email).toBe(EMAIL) + }) + + it("returns running session when PVC and running pod both exist", async () => { + fakePVCs = [makePVC(SESSION_HASH, EMAIL, REPO, BRANCH)] + fakePods = [makeRunningPod(SESSION_HASH, EMAIL, REPO, BRANCH)] + + const result = await listUserSessions(EMAIL, fakeReq) + + expect(result).toHaveLength(1) + expect((result[0] as any).state).toBe("running") + }) + + it("returns creating session when PVC and pending pod exist", async () => { + fakePVCs = [makePVC(SESSION_HASH, EMAIL, REPO, BRANCH)] + fakePods = [makePendingPod(SESSION_HASH, EMAIL, REPO, BRANCH)] + + const result = await listUserSessions(EMAIL, fakeReq) + + expect(result).toHaveLength(1) + expect((result[0] as any).state).toBe("creating") + }) + + it("includes lastActivity and idleTimeoutMinutes in stopped session result", async () => { + const lastActivity = "2025-06-01T10:00:00Z" + fakePVCs = [makePVC(SESSION_HASH, EMAIL, REPO, BRANCH, lastActivity)] + fakePods = [] + + const result = await listUserSessions(EMAIL, fakeReq) + + // WILL FAIL: current impl returns [] (no pods), so result is empty + expect(result).toHaveLength(1) + expect(typeof (result[0] as any).lastActivity).toBe("string") + expect(typeof (result[0] as any).idleTimeoutMinutes).toBe("number") + }) + + it("includes lastActivity and idleTimeoutMinutes in running session result", async () => { + fakePVCs = [makePVC(SESSION_HASH, EMAIL, REPO, BRANCH)] + fakePods = [makeRunningPod(SESSION_HASH, EMAIL, REPO, BRANCH)] + + const result = await listUserSessions(EMAIL, fakeReq) + + expect(result).toHaveLength(1) + // WILL FAIL: SessionInfo currently lacks lastActivity and idleTimeoutMinutes + expect(typeof (result[0] as any).lastActivity).toBe("string") + expect(typeof (result[0] as any).idleTimeoutMinutes).toBe("number") + }) + + it("does not return sessions belonging to a different user", async () => { + fakePVCs = [makePVC(SESSION_HASH, "other@example.com", REPO, BRANCH)] + fakePods = [] + + const result = await listUserSessions(EMAIL, fakeReq) + + expect(result).toHaveLength(0) + }) + + it("returns empty list when no PVCs exist", async () => { + fakePVCs = [] + fakePods = [] + + const result = await listUserSessions(EMAIL, fakeReq) + + expect(result).toHaveLength(0) + }) +}) + +// --------------------------------------------------------------------------- + +describe("PodState type", () => { + it("accepts stopped as a valid PodState value at runtime", () => { + // TypeScript compile-time: "stopped" is NOT assignable to current PodState + // ("none" | "creating" | "running") so we use `as any` to bypass the type + // error and assert at runtime that the string value is what we expect. + // The GREEN phase fix will extend the union so no cast is needed. + const state: import("./pod-manager.js").PodState = "stopped" as any + expect(state as any).toBe("stopped") + }) +}) + +// --------------------------------------------------------------------------- + +describe("terminateSession", () => { + it("deletes pod and PVC when caller is the owner", async () => { + fakePVCs = [makePVC(SESSION_HASH, EMAIL, REPO, BRANCH)] + fakePods = [makeRunningPod(SESSION_HASH, EMAIL, REPO, BRANCH)] + + await (terminateSession as any)(SESSION_HASH, EMAIL) + + // Both pod and PVC should have been removed from the fake store + expect(fakePods).toHaveLength(0) + expect(fakePVCs).toHaveLength(0) + }) + + it("throws Forbidden when caller is not the owner", async () => { + fakePVCs = [makePVC(SESSION_HASH, EMAIL, REPO, BRANCH)] + fakePods = [makeRunningPod(SESSION_HASH, EMAIL, REPO, BRANCH)] + + await expect((terminateSession as any)(SESSION_HASH, "other@example.com")).rejects.toThrow("Forbidden") + + // Nothing should have been deleted + expect(fakePods).toHaveLength(1) + expect(fakePVCs).toHaveLength(1) + }) + + it("throws NotFound when PVC does not exist", async () => { + fakePVCs = [] + fakePods = [] + + await expect((terminateSession as any)(SESSION_HASH, EMAIL)).rejects.toThrow("NotFound") + }) + + it("succeeds even if pod does not exist (idempotent pod delete)", async () => { + fakePVCs = [makePVC(SESSION_HASH, EMAIL, REPO, BRANCH)] + fakePods = [] // no pod — already stopped + + await (terminateSession as any)(SESSION_HASH, EMAIL) + + // PVC should still be removed + expect(fakePVCs).toHaveLength(0) + }) +}) + +// --------------------------------------------------------------------------- + +describe("resumeSession", () => { + it("calls ensurePod for a stopped session (PVC exists, no pod)", async () => { + fakePVCs = [makePVC(SESSION_HASH, EMAIL, REPO, BRANCH)] + fakePods = [] // session is stopped — no pod + + await (resumeSession as any)(SESSION_HASH, EMAIL) + + // ensurePod must have called createNamespacedPod to recreate the pod + expect(createPodCalls).toHaveLength(1) + expect(fakePods).toHaveLength(1) + }) + + it("is idempotent when pod already exists (running session)", async () => { + fakePVCs = [makePVC(SESSION_HASH, EMAIL, REPO, BRANCH)] + fakePods = [makeRunningPod(SESSION_HASH, EMAIL, REPO, BRANCH)] + const podsBefore = fakePods.length + + await (resumeSession as any)(SESSION_HASH, EMAIL) + + // ensurePod should detect existing pod and skip creation + expect(createPodCalls).toHaveLength(0) + expect(fakePods).toHaveLength(podsBefore) + }) + + it("throws Forbidden when caller is not the owner", async () => { + fakePVCs = [makePVC(SESSION_HASH, EMAIL, REPO, BRANCH)] + fakePods = [] + + await expect((resumeSession as any)(SESSION_HASH, "other@example.com")).rejects.toThrow("Forbidden") + + // No pod should have been created + expect(createPodCalls).toHaveLength(0) + }) + + it("throws NotFound when PVC does not exist", async () => { + fakePVCs = [] + fakePods = [] + + await expect((resumeSession as any)(SESSION_HASH, EMAIL)).rejects.toThrow("NotFound") + + expect(createPodCalls).toHaveLength(0) + }) +}) + +// --------------------------------------------------------------------------- + +describe("suggestBranch", () => { + it("returns a string branch name", async () => { + fakePVCs = [] + + const branch = await (suggestBranch as any)(EMAIL, REPO) + + expect(typeof branch).toBe("string") + expect(branch.length).toBeGreaterThan(0) + expect(branch).toMatch(/^[a-z]+-[a-z]+-[a-z]+$/) + }) + + it("skips names that already exist as PVC hashes and returns the next unique one", async () => { + // Mock human-id to return a fixed sequence: first two collide, third is free + const seq = ["calm-snails-dream", "bold-frogs-dance", "swift-hawks-fly"] + let idx = 0 + mock.module("human-id", () => ({ + default: (_opts: object) => seq[idx++ % seq.length], + })) + + // Pre-populate PVCs for the first two candidates so they collide + const hash0 = computeHash(EMAIL, REPO, seq[0]) + const hash1 = computeHash(EMAIL, REPO, seq[1]) + fakePVCs = [makePVC(hash0, EMAIL, REPO, seq[0]), makePVC(hash1, EMAIL, REPO, seq[1])] + + const branch = await (suggestBranch as any)(EMAIL, REPO) + + // Must NOT be one of the colliding names + expect(branch).not.toBe(seq[0]) + expect(branch).not.toBe(seq[1]) + expect(branch).toBe(seq[2]) + }) + + it("returns after at most 10 iterations even if all collide", async () => { + // Mock human-id to always return the same name (infinite collision) + const fixed = "slow-bears-roam" + mock.module("human-id", () => ({ + default: (_opts: object) => fixed, + })) + + // Pre-populate a PVC for that name so every attempt collides + const hash = computeHash(EMAIL, REPO, fixed) + fakePVCs = [makePVC(hash, EMAIL, REPO, fixed)] + + // Should resolve (not hang) after 10 attempts and return the last candidate + const branch = await (suggestBranch as any)(EMAIL, REPO) + + expect(typeof branch).toBe("string") + expect(branch).toBe(fixed) + }) +}) + +// --------------------------------------------------------------------------- +// 1.3.14: branch handling — sourceBranch vs sessionBranch +// --------------------------------------------------------------------------- + +describe("SessionKey.sourceBranch", () => { + it("listUserSessions includes sourceBranch from PVC annotation", async () => { + // PVC must store sourceBranch annotation; listUserSessions must return it + const pvc = makePVC(SESSION_HASH, EMAIL, REPO, BRANCH) + // Add sourceBranch annotation (will fail until annotation is stored/read) + ;(pvc as any).metadata.annotations["opencode.ai/source-branch"] = "main" + fakePVCs = [pvc] + fakePods = [] + + const result = await listUserSessions(EMAIL, fakeReq) + + expect(result).toHaveLength(1) + // WILL FAIL: SessionInfo currently has no sourceBranch field + expect((result[0] as any).sourceBranch).toBe("main") + }) + + it("ensurePod git-init script checks out sourceBranch then creates sessionBranch", async () => { + // After creating a pod, the init script must contain both sourceBranch and branch + fakePVCs = [] + fakePods = [] + createPodCalls = [] + + const { ensurePod } = await import("./pod-manager.js") + + // WILL FAIL: SessionKey currently has no sourceBranch field, so TypeScript + // will reject this call — but at runtime the script won't contain sourceBranch + await (ensurePod as any)({ email: EMAIL, repoUrl: REPO, branch: "calm-snails-dream", sourceBranch: "main" }) + + expect(createPodCalls).toHaveLength(1) + const pod = (createPodCalls[0] as any).body + const script: string = pod.spec.initContainers[0].args[0] + // Script must checkout the sourceBranch before creating the new sessionBranch + expect(script).toContain("main") // sourceBranch checkout + expect(script).toContain("calm-snails-dream") // new session branch creation + // Must NOT try to look up "calm-snails-dream" on remote (it's always a new branch) + expect(script).not.toContain('ls-remote --exit-code --heads origin "calm-snails-dream"') + }) +}) From 24d932b27f20882d33e116bddfff38e9a2e2fe81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Mon, 13 Apr 2026 09:34:08 +0200 Subject: [PATCH 008/616] chore: vibe development plan and beads task state --- .beads/README.md | 81 ++++++++ .beads/config.yaml | 62 ++++++ .beads/interactions.jsonl | 0 .beads/issues.jsonl | 37 ++++ .beads/last-touched | 1 + .beads/metadata.json | 4 + .vibe/.gitignore | 5 + ...te-opencode-session-management-s6s97r.json | 29 +++ .vibe/development-plan-session-management.md | 181 ++++++++++++++++++ 9 files changed, 400 insertions(+) create mode 100644 .beads/README.md create mode 100644 .beads/config.yaml create mode 100644 .beads/interactions.jsonl create mode 100644 .beads/issues.jsonl create mode 100644 .beads/last-touched create mode 100644 .beads/metadata.json create mode 100644 .vibe/.gitignore create mode 100644 .vibe/beads-state-opencode-session-management-s6s97r.json create mode 100644 .vibe/development-plan-session-management.md diff --git a/.beads/README.md b/.beads/README.md new file mode 100644 index 000000000000..50f281f032bb --- /dev/null +++ b/.beads/README.md @@ -0,0 +1,81 @@ +# Beads - AI-Native Issue Tracking + +Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code. + +## What is Beads? + +Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git. + +**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads) + +## Quick Start + +### Essential Commands + +```bash +# Create new issues +bd create "Add user authentication" + +# View all issues +bd list + +# View issue details +bd show + +# Update issue status +bd update --status in_progress +bd update --status done + +# Sync with git remote +bd sync +``` + +### Working with Issues + +Issues in Beads are: +- **Git-native**: Stored in `.beads/issues.jsonl` and synced like code +- **AI-friendly**: CLI-first design works perfectly with AI coding agents +- **Branch-aware**: Issues can follow your branch workflow +- **Always in sync**: Auto-syncs with your commits + +## Why Beads? + +✨ **AI-Native Design** +- Built specifically for AI-assisted development workflows +- CLI-first interface works seamlessly with AI coding agents +- No context switching to web UIs + +🚀 **Developer Focused** +- Issues live in your repo, right next to your code +- Works offline, syncs when you push +- Fast, lightweight, and stays out of your way + +🔧 **Git Integration** +- Automatic sync with git commits +- Branch-aware issue tracking +- Intelligent JSONL merge resolution + +## Get Started with Beads + +Try Beads in your own projects: + +```bash +# Install Beads +curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash + +# Initialize in your repo +bd init + +# Create your first issue +bd create "Try out Beads" +``` + +## Learn More + +- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs) +- **Quick Start Guide**: Run `bd quickstart` +- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples) + +--- + +*Beads: Issue tracking that moves at the speed of thought* ⚡ diff --git a/.beads/config.yaml b/.beads/config.yaml new file mode 100644 index 000000000000..259d79de0ce1 --- /dev/null +++ b/.beads/config.yaml @@ -0,0 +1,62 @@ +# Beads Configuration File +# This file configures default behavior for all bd commands in this repository +# All settings can also be set via environment variables (BD_* prefix) +# or overridden with command-line flags + +# Issue prefix for this repository (used by bd init) +# If not set, bd init will auto-detect from directory name +# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc. +issue-prefix: "opencode" + +# Use no-db mode: load from JSONL, no SQLite, write back after each command +# When true, bd will use .beads/issues.jsonl as the source of truth +# instead of SQLite database +no-db: true # JSONL-only mode, no SQLite database + +# Disable daemon for RPC communication (forces direct database access) +# no-daemon: false + +# Disable auto-flush of database to JSONL after mutations +# no-auto-flush: false + +# Disable auto-import from JSONL when it's newer than database +# no-auto-import: false + +# Enable JSON output by default +# json: false + +# Default actor for audit trails (overridden by BD_ACTOR or --actor) +# actor: "" + +# Path to database (overridden by BEADS_DB or --db) +# db: "" + +# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON) +# auto-start-daemon: true + +# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE) +# flush-debounce: "5s" + +# Git branch for beads commits (bd sync will commit to this branch) +# IMPORTANT: Set this for team projects so all clones use the same sync branch. +# This setting persists across clones (unlike database config which is gitignored). +# Can also use BEADS_SYNC_BRANCH env var for local override. +# If not set, bd sync will require you to run 'bd config set sync.branch '. +# sync-branch: "beads-sync" + +# Multi-repo configuration (experimental - bd-307) +# Allows hydrating from multiple repositories and routing writes to the correct JSONL +# repos: +# primary: "." # Primary repo (where this database lives) +# additional: # Additional repos to hydrate from (read-only) +# - ~/beads-planning # Personal planning repo +# - ~/work-planning # Work planning repo + +# Integration settings (access with 'bd config get/set') +# These are stored in the database, not in this file: +# - jira.url +# - jira.project +# - linear.url +# - linear.api-key +# - github.org +# - github.repo diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl new file mode 100644 index 000000000000..5fc335327d94 --- /dev/null +++ b/.beads/issues.jsonl @@ -0,0 +1,37 @@ +{"id":"opencode-1","title":"opencode: epcc (development-plan-session-management.md)","description":"Responsible vibe engineering session using epcc workflow for opencode","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:25:06.198656+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:25:06.198656+02:00"} +{"id":"opencode-1.1","title":"Explore","description":"Research the codebase to understand existing patterns and gather context about the problem space. - If uncertain about conventions or rules, ask the user about them - Read relevant files and documentation - If exists: Understand and document requirements there - Otherwise: Document requirements in your task management system Focus on understanding without writing code yet. Document your findings and create tasks as needed.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:25:06.371409+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:25:06.371409+02:00","dependencies":[{"issue_id":"opencode-1.1","depends_on_id":"opencode-1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.1.1","title":"Understand current PVC creation flow","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:25:59.541289+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:26:03.791742+02:00","closed_at":"2026-04-13T00:26:03.791742+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.1.1","depends_on_id":"opencode-1.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.1.2","title":"Understand session termination/suspension behavior","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:25:59.714031+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:26:03.907102+02:00","closed_at":"2026-04-13T00:26:03.907102+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.1.2","depends_on_id":"opencode-1.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.1.3","title":"Understand session resume behavior","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:25:59.878265+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:26:04.035057+02:00","closed_at":"2026-04-13T00:26:04.035057+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.1.3","depends_on_id":"opencode-1.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.1.4","title":"Identify gaps in session lifecycle management","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:26:00.051351+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:26:28.00375+02:00","closed_at":"2026-04-13T00:26:28.00375+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.1.4","depends_on_id":"opencode-1.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2","title":"Plan","description":"Create a detailed implementation strategy based on your exploration: - If exists: Base your strategy on requirements from it - Otherwise: Use existing task context Break down the work into specific, actionable tasks. Consider edge cases, dependencies, and potential challenges. - If architectural changes needed and exists: Document in - Otherwise: Create tasks to track architectural decisions - If exists: Adhere to the design in it - Otherwise: Elaborate design options and present them to the user Document the planning work thoroughly and create implementation tasks as part of the code phase as needed.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:25:06.527716+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:25:06.527716+02:00","dependencies":[{"issue_id":"opencode-1.2","depends_on_id":"opencode-1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"opencode-1.2","depends_on_id":"opencode-1.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2.1","title":"pod-manager: add stopped state + listUserSessions from PVCs","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:40:59.806542+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:40:59.806542+02:00","dependencies":[{"issue_id":"opencode-1.2.1","depends_on_id":"opencode-1.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2.10","title":"router-app api.ts: add stopped state, resume, terminate, suggestBranch","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:41:01.077758+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:41:01.077758+02:00","dependencies":[{"issue_id":"opencode-1.2.10","depends_on_id":"opencode-1.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2.11","title":"router-app app.tsx: show stopped sessions, idle countdown, Resume and Terminate buttons","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:41:01.215128+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:41:01.215128+02:00","dependencies":[{"issue_id":"opencode-1.2.11","depends_on_id":"opencode-1.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2.12","title":"router-app setup-form.tsx: pre-fill branch from suggest-branch endpoint","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:41:01.35417+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:41:01.35417+02:00","dependencies":[{"issue_id":"opencode-1.2.12","depends_on_id":"opencode-1.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2.13","title":"deployment: add delete verb on PVCs to RBAC role","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:41:01.496083+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:41:01.496083+02:00","dependencies":[{"issue_id":"opencode-1.2.13","depends_on_id":"opencode-1.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2.2","title":"pod-manager: add terminateSession (delete pod + PVC)","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:40:59.938205+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:40:59.938205+02:00","dependencies":[{"issue_id":"opencode-1.2.2","depends_on_id":"opencode-1.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2.3","title":"pod-manager: add resumeSession (ensurePod for stopped session)","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:41:00.085445+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:41:00.085445+02:00","dependencies":[{"issue_id":"opencode-1.2.3","depends_on_id":"opencode-1.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2.4","title":"pod-manager: add suggestBranch using human-id package","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:41:00.222026+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:41:00.222026+02:00","dependencies":[{"issue_id":"opencode-1.2.4","depends_on_id":"opencode-1.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2.5","title":"api.ts: add DELETE /api/sessions/:hash (terminate)","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:41:00.370296+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:41:00.370296+02:00","dependencies":[{"issue_id":"opencode-1.2.5","depends_on_id":"opencode-1.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2.6","title":"api.ts: add POST /api/sessions/:hash/resume","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:41:00.508+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:41:00.508+02:00","dependencies":[{"issue_id":"opencode-1.2.6","depends_on_id":"opencode-1.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2.7","title":"api.ts: add GET /api/sessions/suggest-branch?repoUrl=","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:41:00.647995+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:41:00.647995+02:00","dependencies":[{"issue_id":"opencode-1.2.7","depends_on_id":"opencode-1.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2.8","title":"api.ts: include lastActivity + idleTimeoutMinutes in session list and get","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:41:00.795056+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:41:00.795056+02:00","dependencies":[{"issue_id":"opencode-1.2.8","depends_on_id":"opencode-1.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.2.9","title":"config.ts: reduce default IDLE_TIMEOUT_MINUTES from 30 to 15","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:41:00.930236+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:41:00.930236+02:00","dependencies":[{"issue_id":"opencode-1.2.9","depends_on_id":"opencode-1.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3","title":"Code","description":"Follow your plan to build the solution: - If exists: Follow the design from it - Otherwise: Elaborate design options and present them to the user - If exists: Build according to the architecture from it - Otherwise: Elaborate architectural options and present them to the user - If exists: Ensure requirements from it are met - Otherwise: Ensure existing requirements are met based on your task context Write clean, well-structured code with proper error handling. Prevent regression by building, linting, and executing existing tests. Stay flexible and adapt the plan as you learn more during implementation. Update task progress and create new tasks as needed.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:25:06.673492+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:25:06.673492+02:00","dependencies":[{"issue_id":"opencode-1.3","depends_on_id":"opencode-1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"opencode-1.3","depends_on_id":"opencode-1.2","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.1","title":"config.ts: reduce default IDLE_TIMEOUT_MINUTES from 30 to 15","description":"One-line change in packages/opencode-router/src/config.ts:\n Change: Number(process.env.IDLE_TIMEOUT_MINUTES ?? 30)\n To: Number(process.env.IDLE_TIMEOUT_MINUTES ?? 15)\n Independent of all other tasks — can land first.","status":"closed","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:45:07.556459+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:53:59.247046+02:00","closed_at":"2026-04-13T00:53:59.247046+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.1","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.10","title":"router-app api.ts: update types + add resume, terminate, suggestBranch","description":"File: packages/opencode-router-app/src/api.ts\nDepends on: backend tasks opencode-1.3.6/7/8/9\n\nChanges:\n1. Session interface:\n - state union: \"creating\" | \"running\" | \"stopped\"\n - add lastActivity: string | null\n - add idleTimeoutMinutes: number\n\n2. Add resumeSession:\n export async function resumeSession(hash: string): Promise\u003c{ hash: string; url: string; state: string }\u003e\n → POST /api/sessions/:hash/resume\n → throws on non-ok response\n\n3. Add terminateSession:\n export async function terminateSession(hash: string): Promise\u003cvoid\u003e\n → DELETE /api/sessions/:hash\n → throws on non-ok response\n\n4. Add suggestBranch:\n export async function suggestBranch(repoUrl: string): Promise\u003c{ branch: string }\u003e\n → GET /api/sessions/suggest-branch?repoUrl=\u003cencoded\u003e\n → throws on non-ok response","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:46:18.02057+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:13:32.696782+02:00","closed_at":"2026-04-13T01:13:32.696782+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.10","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.11","title":"router-app app.tsx: stopped sessions, idle countdown, Resume + Terminate","description":"File: packages/opencode-router-app/src/app.tsx\nDepends on: opencode-1.3.10\n\nChanges:\n\n1. Session card rendering — replace the single \u003ca\u003e with conditional rendering:\n - state \"running\" or \"creating\": keep \u003ca href={session.url}\u003e wrapping the card\n - state \"stopped\": render a plain \u003cdiv\u003e (no link — no pod to navigate to)\n\n2. Status line text (the second \u003cp\u003e below the repo name):\n - \"creating\" → \"starting…\"\n - \"running\" → \"stops in ~Xm\" where X = Math.max(0, Math.round(\n (new Date(session.lastActivity!).getTime() + session.idleTimeoutMinutes * 60000 - Date.now()) / 60000\n ))\n - \"stopped\" → session.lastActivity\n ? \"stopped \" + Math.round((Date.now() - new Date(session.lastActivity).getTime()) / 60000) + \"m ago\"\n : \"stopped\"\n\n3. Add Resume button (only when state === \"stopped\"):\n \u003cButton variant=\"secondary\" size=\"small\" onClick={handleResume(session)}\u003eResume\u003c/Button\u003e\n handleResume: calls resumeSession(hash) → setPhase({ kind: \"creating\", hash, url })\n On error: show inline error message on that card.\n\n4. Add Terminate button (always visible on each card):\n \u003cButton variant=\"danger\" (or \"secondary\") size=\"small\" onClick={handleTerminate(session)}\u003eTerminate\u003c/Button\u003e\n handleTerminate: optimistically remove session from list signal, call terminateSession(hash).\n On error: reload full session list via loadSessions().\n No native confirm() dialog — use an inline \"Are you sure?\" toggle state on the card\n (a confirmingHash signal: string | null) to avoid blocking the UI.","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:46:30.990236+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:23:30.116573+02:00","closed_at":"2026-04-13T01:23:30.116573+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.11","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.12","title":"router-app setup-form.tsx: auto-fill branch from suggest-branch endpoint","description":"File: packages/opencode-router-app/src/setup-form.tsx\nDepends on: opencode-1.3.10\n\nChanges:\n1. Add signal: const [branchTouched, setBranchTouched] = createSignal(false)\n Set to true in the branch TextField onChange handler (user typed something).\n\n2. Add effect that fires when repoUrl() changes:\n createEffect(() =\u003e {\n const url = repoUrl().trim()\n if (!GIT_URL_PATTERN.test(url)) return\n if (branchTouched()) return // do not overwrite user input\n suggestBranch(url)\n .then(({ branch }) =\u003e { if (!branchTouched()) setBranch(branch) })\n .catch(() =\u003e {}) // silent fail — branch field stays empty\n })\n\n3. Show a subtle loading indicator while suggestion is in-flight:\n Add signal: const [suggesting, setSuggesting] = createSignal(false)\n Wrap suggestBranch call with setSuggesting(true/false).\n Add placeholder or helper text: suggesting() ? \"generating…\" : \"main\"","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:46:40.114632+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:23:30.22637+02:00","closed_at":"2026-04-13T01:23:30.22637+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.12","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.13","title":"deployment: add delete verb on PVCs to RBAC role","description":"File: deployment/homelab/src/index.ts\nIndependent — can land at any time.\n\nFind the persistentvolumeclaims rule in the Role resource (around line 107):\n {\n apiGroups: [\"\"],\n resources: [\"persistentvolumeclaims\"],\n verbs: [\"get\", \"list\", \"create\"],\n },\n\nChange to:\n {\n apiGroups: [\"\"],\n resources: [\"persistentvolumeclaims\"],\n verbs: [\"get\", \"list\", \"create\", \"delete\"],\n }\n\nWithout this, terminateSession will receive a 403 from the k8s API when attempting\nto delete a PVC.","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:46:46.507099+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:24:20.401007+02:00","closed_at":"2026-04-13T01:24:20.401007+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.13","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.14","title":"fix branch handling: sourceBranch (user input) vs sessionBranch (auto-generated, display-only)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T01:25:30.714458+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:30:24.020258+02:00","closed_at":"2026-04-13T01:30:24.020258+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.14","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.15","title":"dev-proxy: auto kubectl port-forward for local dev","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T07:22:03.893881+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T07:22:08.546124+02:00","closed_at":"2026-04-13T07:22:08.546124+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.15","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.2","title":"pod-manager: stopped state + listUserSessions from PVCs","description":"File: packages/opencode-router/src/pod-manager.ts\n\n1. Extend PodState type:\n export type PodState = \"none\" | \"creating\" | \"running\" | \"stopped\"\n\n2. Extend SessionInfo interface with two new fields:\n lastActivity: string | null // ISO timestamp from pod annotation; null when stopped with no record\n idleTimeoutMinutes: number // from config.idleTimeoutMinutes\n\n3. Rewrite listUserSessions to scan PVCs as source of truth:\n - List PVCs with labelSelector: app.kubernetes.io/managed-by=opencode-router\n - Filter by ANNOTATION_USER_EMAIL === email\n - List ALL pods once (same label selector), build a Map\u003chash, V1Pod\u003e to avoid N+1 calls\n - For each PVC, look up its hash from labels[LABEL_SESSION_HASH]:\n - No pod found → state = \"stopped\"\n - Pod found, phase=Running + podIP set → state = \"running\"\n - Pod found, otherwise → state = \"creating\"\n - lastActivity: read from pod annotation if pod exists; null otherwise\n - Edge: skip PVCs whose hash label is missing or not 12 hex chars","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:45:17.566902+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:57:55.934639+02:00","closed_at":"2026-04-13T00:57:55.934639+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.2","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.3","title":"pod-manager: add terminateSession (delete pod + PVC)","description":"File: packages/opencode-router/src/pod-manager.ts\nDepends on: opencode-1.3.2 (stopped state)\n\nAdd export:\n export async function terminateSession(hash: string, email: string): Promise\u003cvoid\u003e\n\nSteps:\n 1. Read PVC by pvcName(hash). If 404 → throw { code: 404 }.\n 2. Check PVC annotations[ANNOTATION_USER_EMAIL] === email. If mismatch → throw { code: 403 }.\n 3. Delete pod by podName(hash) — ignore 404 (already gone for stopped sessions).\n 4. Delete PVC by pvcName(hash).\n Note: if pod is still terminating and has the volume mounted, k8s will queue the PVC\n deletion via finalizer — this is acceptable, do not retry or poll.\n 5. activityThrottle.delete(hash)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:45:25.433313+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:06:51.180396+02:00","closed_at":"2026-04-13T01:06:51.180396+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.3","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.4","title":"pod-manager: add resumeSession (recreate pod for stopped session)","description":"File: packages/opencode-router/src/pod-manager.ts\nDepends on: opencode-1.3.2\n\nAdd export:\n export async function resumeSession(session: SessionKey): Promise\u003cstring\u003e\n\nSteps:\n 1. Compute hash = getSessionHash(session.email, session.repoUrl, session.branch)\n 2. Read PVC by pvcName(hash). If 404 → throw { code: 404 } (session was terminated).\n 3. Call ensurePod(session) — already idempotent; creates pod if absent, no-op if running.\n 4. Return hash.\n\nThe caller (api.ts) returns { hash, url, state: \"creating\" } — the frontend reuses\nthe existing LoadingScreen polling loop to wait for Running.","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:45:32.624951+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:06:51.30885+02:00","closed_at":"2026-04-13T01:06:51.30885+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.4","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.5","title":"pod-manager: add suggestBranch (collision-safe human-id name)","description":"File: packages/opencode-router/src/pod-manager.ts\nDepends on: opencode-1.3.2\n\nPre-step: add human-id to dependencies in packages/opencode-router/package.json\n \"human-id\": \"^1.0.2\" (MIT, zero deps, adjective-noun-verb format, ~15M combinations)\n Then run: bun install (from packages/opencode-router)\n\nAdd import at top of pod-manager.ts:\n import { humanId } from \"human-id\"\n\nAdd export:\n export async function suggestBranch(email: string, repoUrl: string): Promise\u003cstring\u003e\n\nImplementation:\n let branch = \"\"\n for (let i = 0; i \u003c 10; i++) {\n branch = humanId({ separator: \"-\", capitalize: false })\n const hash = getSessionHash(email, repoUrl, branch)\n try {\n await k8sApi.readNamespacedPersistentVolumeClaim({ name: pvcName(hash), namespace: config.namespace })\n // PVC exists — collision, try next\n } catch (err) {\n if (isNotFound(err)) return branch // no collision — use this name\n throw err\n }\n }\n return branch // after 10 attempts just return last (collision astronomically unlikely)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:45:41.200816+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:06:51.452089+02:00","closed_at":"2026-04-13T01:06:51.452089+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.5","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.6","title":"api.ts: DELETE /api/sessions/:hash (terminate)","description":"File: packages/opencode-router/src/api.ts\nDepends on: opencode-1.3.3\n\nImport terminateSession from pod-manager.\n\nAdd route handler (place BEFORE the existing GET /api/sessions/:hash handler):\n Pattern: /^\\/api\\/sessions\\/([a-f0-9]{12})$/ + method DELETE\n Steps:\n 1. Extract hash from match[1]\n 2. Call terminateSession(hash, email)\n 3. On success → json(res, 204, {}) — actually res.writeHead(204).end()\n 4. Catch { code: 404 } → json(res, 404, { error: \"session not found\" })\n 5. Catch { code: 403 } → json(res, 403, { error: \"forbidden\" })","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:45:48.434784+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:10:59.496544+02:00","closed_at":"2026-04-13T01:10:59.496544+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.6","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.7","title":"api.ts: POST /api/sessions/:hash/resume","description":"File: packages/opencode-router/src/api.ts\nDepends on: opencode-1.3.4\n\nImport resumeSession from pod-manager.\nAlso import k8sApi or a helper to read PVC annotations — or add a helper\ngetPVCSession(hash): Promise\u003cSessionKey\u003e to pod-manager that reads PVC annotations\nand returns { email, repoUrl, branch }.\n\nAdd route handler:\n Pattern: /^\\/api\\/sessions\\/([a-f0-9]{12})\\/resume$/ + method POST\n Steps:\n 1. Extract hash from match[1]\n 2. Read PVC to reconstruct SessionKey (email, repoUrl, branch from annotations)\n Verify PVC annotations[ANNOTATION_USER_EMAIL] === email → 403 if mismatch\n PVC not found → 404\n 3. Call resumeSession({ email, repoUrl, branch })\n 4. Return json(res, 200, { hash, url: sessionUrl(hash, req), state: \"creating\" })\n\nNote: \"creating\" is always safe to return even if pod was already running — the\nLoadingScreen will poll and redirect once Running.","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:45:55.929655+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:10:59.664823+02:00","closed_at":"2026-04-13T01:10:59.664823+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.7","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.8","title":"api.ts: GET /api/sessions/suggest-branch?repoUrl=","description":"File: packages/opencode-router/src/api.ts\nDepends on: opencode-1.3.5\n\nImport suggestBranch from pod-manager.\n\nAdd route handler (place BEFORE /api/sessions/:hash handlers — belt-and-suspenders\neven though \"suggest-branch\" cannot match the 12-hex-char hash regex):\n Pattern: url === \"/api/sessions/suggest-branch\" + method GET\n Steps:\n 1. Parse repoUrl from query string: new URL(url, \"http://x\").searchParams.get(\"repoUrl\")\n 2. If !repoUrl → json(res, 400, { error: \"repoUrl is required\" })\n 3. Call suggestBranch(email, repoUrl)\n 4. Return json(res, 200, { branch })","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:46:02.025989+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:10:59.792293+02:00","closed_at":"2026-04-13T01:10:59.792293+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.8","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.3.9","title":"api.ts: include lastActivity + idleTimeoutMinutes in session list and get","description":"File: packages/opencode-router/src/api.ts\nDepends on: opencode-1.3.2 (SessionInfo now has lastActivity + idleTimeoutMinutes)\n\nTwo changes:\n1. GET /api/sessions — no change needed: listUserSessions already returns the full\n SessionInfo array; the new fields flow through automatically.\n\n2. GET /api/sessions/:hash — currently returns { hash, state, url }.\n Change to also include lastActivity and idleTimeoutMinutes.\n Call getPodState(hash) is insufficient alone — need lastActivity too.\n Options:\n a) Add a new pod-manager helper getSessionInfo(hash, req) that returns full SessionInfo\n b) Keep getPodState but also call a new getLastActivity(hash) helper\n Prefer (a): add export getSessionInfo(hash: string, req): Promise\u003cSessionInfo | null\u003e\n to pod-manager that reads the pod (or PVC if pod absent) and returns full info.\n Return 404 if neither PVC nor pod exists.","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:46:10.91948+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:10:59.939602+02:00","closed_at":"2026-04-13T01:10:59.939602+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.9","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-1.4","title":"Commit","description":"Ensure code quality and documentation accuracy through systematic cleanup and review. **STEP 1: Code Cleanup** Systematically clean up development artifacts: 1. **Remove Debug Output**: Search for and remove all temporary debug output statements used during development. Look for language-specific debug output methods (console logging, print statements, debug output functions). Remove any debugging statements that were added for development purposes. 2. **Review TODO/FIXME Comments**: - Address each TODO/FIXME comment by either implementing the solution or documenting why it's deferred - Remove completed TODOs - Convert remaining TODOs to proper issue tracking if needed 3. **Remove Debugging Code Blocks**: - Remove temporary debugging code, test code blocks, and commented-out code - Clean up any experimental code that's no longer needed - Ensure proper error handling replaces temporary debug logging **STEP 2: Documentation Review** Review and update documentation to reflect final implementation: 1. **Update Long-Term Memory Documents**: Based on what was actually implemented: - If exists: Update it if requirements changed during development - If exists: Update it if architectural impacts were identified - If exists: Update it if design details were refined or changed - Otherwise: Document any changes in the plan file 2. **Compare Against Implementation**: Review documentation against actual implemented functionality 3. **Update Changed Sections**: Only modify documentation sections that have functional changes 4. **Remove Development Progress**: Remove references to development iterations, progress notes, and temporary decisions 5. **Focus on Final State**: Ensure documentation describes the final implemented state, not the development process 6. **Ask User to Review Document Updates** **STEP 3: Final Validation** - Run existing tests to ensure cleanup didn't break functionality - Verify documentation accuracy with a final review - Ensure code is ready for production/delivery Update task progress and mark completed work as you finalize the feature.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:25:06.854241+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:25:06.854241+02:00","dependencies":[{"issue_id":"opencode-1.4","depends_on_id":"opencode-1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"opencode-1.4","depends_on_id":"opencode-1.3","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]} diff --git a/.beads/last-touched b/.beads/last-touched new file mode 100644 index 000000000000..69ef5d51785e --- /dev/null +++ b/.beads/last-touched @@ -0,0 +1 @@ +opencode-1.3.15 diff --git a/.beads/metadata.json b/.beads/metadata.json new file mode 100644 index 000000000000..c787975e1ff5 --- /dev/null +++ b/.beads/metadata.json @@ -0,0 +1,4 @@ +{ + "database": "beads.db", + "jsonl_export": "issues.jsonl" +} \ No newline at end of file diff --git a/.vibe/.gitignore b/.vibe/.gitignore new file mode 100644 index 000000000000..2e22035af470 --- /dev/null +++ b/.vibe/.gitignore @@ -0,0 +1,5 @@ +# Exclude conversation state files +conversations/ +# Legacy SQLite files (for migration compatibility) +*.sqlite +*.sqlite-* diff --git a/.vibe/beads-state-opencode-session-management-s6s97r.json b/.vibe/beads-state-opencode-session-management-s6s97r.json new file mode 100644 index 000000000000..7f40f041b784 --- /dev/null +++ b/.vibe/beads-state-opencode-session-management-s6s97r.json @@ -0,0 +1,29 @@ +{ + "conversationId": "opencode-session-management-s6s97r", + "projectPath": "/Users/oliverjaegle/projects/open-source/opencode", + "epicId": "opencode-1", + "phaseTasks": [ + { + "phaseId": "explore", + "phaseName": "Explore", + "taskId": "opencode-1.1" + }, + { + "phaseId": "plan", + "phaseName": "Plan", + "taskId": "opencode-1.2" + }, + { + "phaseId": "code", + "phaseName": "Code", + "taskId": "opencode-1.3" + }, + { + "phaseId": "commit", + "phaseName": "Commit", + "taskId": "opencode-1.4" + } + ], + "createdAt": "2026-04-12T22:25:07.262Z", + "updatedAt": "2026-04-12T22:25:07.262Z" +} \ No newline at end of file diff --git a/.vibe/development-plan-session-management.md b/.vibe/development-plan-session-management.md new file mode 100644 index 000000000000..01992784693d --- /dev/null +++ b/.vibe/development-plan-session-management.md @@ -0,0 +1,181 @@ +# Development Plan: opencode (session-management branch) + +_Generated on 2026-04-12 by Vibe Feature MCP_ +_Workflow: [epcc](https://mrsimpson.github.io/responsible-vibe-mcp/workflows/epcc)_ + +## Goal + +Improve session lifecycle management in the `opencode-router`: + +1. **PVC creation** — currently PVCs are always created eagerly on session creation. We want fine-grained control: understand and document when/how PVCs are created, and ensure the process is robust. +2. **Session termination vs. suspension** — currently idle pods are hard-deleted after a timeout. We want a distinction between "suspend" (delete pod, keep PVC, session resumable) and "terminate" (delete pod + PVC, unrecoverable). The API and UI should expose both. +3. **Session resume** — currently a returning user triggers `ensurePod` implicitly when they navigate to the session URL. We want an explicit "resume" action that lets users resume a suspended session from the sessions list without manually navigating to the URL. + +## Key Decisions + +- **PVC = Session identity**: the PVC is the durable record of a session. The pod is ephemeral compute. A session exists as long as its PVC exists. +- **Session state**: derived by correlating PVC existence with pod state → `"creating" | "running" | "stopped"`. `"stopped"` means PVC exists, no pod (idle-cleaned or never started after creation). No new `PodState` enum values needed. +- **List sessions**: `listUserSessions` scans PVCs (not pods) as the primary source of truth, then correlates each PVC with its pod state. +- **Resume action**: `POST /api/sessions/:hash/resume` — recreates the pod for an existing PVC (calls `ensurePod`). Idempotent. +- **Terminate action**: `DELETE /api/sessions/:hash` — explicit, destructive: deletes pod (if present) + PVC. Removes entry from activityThrottle. +- **Idle cleanup unchanged**: continues to delete only the pod; session becomes `"stopped"` and is resumable. +- **Idle timeout**: default reduced to `15` minutes (was 30). Still configured via `IDLE_TIMEOUT_MINUTES`. No per-session override. +- **Transparency**: `SessionInfo` returned by `listUserSessions` and `GET /api/sessions/:hash` must include `lastActivity` (ISO timestamp) and the configured `idleTimeoutMinutes`. The UI computes "stops in ~Xm" for running sessions from these two values. +- **Branch name generation**: server-side, via `human-id` npm package (zero deps, MIT, 15M combinations). Format: `adjective-noun-verb` with `-` separator, lowercase (e.g. `calm-snails-dream`). New endpoint `GET /api/sessions/suggest-branch?email=...&repoUrl=...` generates names until it finds one whose `(email, repoUrl, branch)` hash does not collide with an existing PVC. Frontend pre-fills the branch field with the suggestion; user can still edit it. +- **Authorization**: session owner only (email from `X-Auth-Request-Email` must match PVC annotation). No admin role in the API — k8s API serves that purpose. +- **Session listing scope**: always scoped to the calling user. No cross-user listing. + +### Correction: branch handling (task 1.3.14) + +The initial implementation conflated two distinct concepts under `branch`: + +- **`sourceBranch`** — the branch the user wants to start from (e.g. `main`). User must enter this. Used only in the init container to set the starting point before creating the session branch. +- **`sessionBranch`** (aka `branch` in `SessionKey`) — the unique branch created for this session (e.g. `calm-snails-dream`). Auto-generated via `suggestBranch`, display-only in the UI for now. This is the branch that ends up in the PVC annotation and forms part of the session identity hash. + +The git init container must: + +1. Clone `repoUrl` if not already cloned +2. Fetch all +3. Checkout `sourceBranch` (the user's starting point) +4. Create new branch `sessionBranch` from there (always a new local branch) + +`SessionKey` gains a `sourceBranch` field. `getSessionHash` continues to use `(email, repoUrl, branch)` where `branch` = sessionBranch. The form shows: repo URL field, source branch field ("start from"), and a display-only session branch label ("your session branch: calm-snails-dream"). `suggestBranch` is called after the user enters a valid repo URL. + +### Implementation decisions (Code phase) + +- **sourceBranch vs sessionBranch split (1.3.14)**: `SessionKey.sourceBranch` = user-entered branch to start from (e.g. `main`). `SessionKey.branch` = auto-generated session branch (e.g. `calm-snails-dream`), stored in PVC annotation, forms the identity hash. Git init: clone → checkout `sourceBranch` → `git checkout -b sessionBranch`. Session branches are always new; no remote lookup needed. Both annotations stored on PVC and pod for resumeSession reconstruction. `createSession` API now requires `{ repoUrl, branch, sourceBranch }`. Form shows repo URL + source branch fields + display-only session branch label. +- **`human-id` type import**: the package is CJS, and with `moduleResolution: nodenext` the default export isn't typed as callable. Cast at call site: `(humanId as unknown as (opts?: object) => string)`. +- **`GET /api/sessions/:hash` enrichment**: instead of a new `getSessionInfo` function in pod-manager, the handler calls `listUserSessions(email, req)` and finds the matching hash. Falls back to `getPodState` + synthesized fields for sessions not owned by the requesting user. +- **Route ordering**: `suggest-branch` route checked before `/:hash` regex in `api.ts` (the path is not 12 hex chars so won't false-match, but explicit ordering is clearer). +- **Testing SolidJS components**: No component rendering tests — pure logic is extracted to separate files (`session-utils.ts`, `setup-form-utils.ts`) with no SolidJS imports, following the pattern used throughout the `packages/app` and `packages/ui` packages. Component files import and re-export these utilities; tests import only the utility files. +- **`bun:test` types in router-app**: added `bun-types` dev dependency and `"bun-types"` to `tsconfig.json` `types` array to fix TypeScript errors in test files. +- **`setup-form-utils.ts` split**: `buildSessionKey` lives in a standalone pure module to avoid the Kobalte/SolidJS server-side crash when bun imports the component file in tests. +- **Dev-mode pod proxy (1.3.15)**: Locally, pod IPs are cluster-internal and unreachable. New `dev-proxy.ts` module auto-spawns `kubectl port-forward` per session hash on first request, caches the local port, and returns `http://localhost:` as proxy target. Enabled when any dev env var (`DEV_EMAIL`, `DEV_VITE_URL`, `DEV_POD_PROXY_TARGET`) is set. `DEV_POD_PROXY_TARGET` still works as a single-target override. Forwards are cleaned up on shutdown. + +## Notes + +### Current codebase snapshot (Explore phase findings) + +**`packages/opencode-router/src/pod-manager.ts`** + +- `ensurePVC(session)` — idempotent, creates PVC if absent. Called from `POST /api/sessions`. +- `ensurePod(session)` — idempotent, creates Pod if absent. Called from `POST /api/sessions`. +- `getPodState(hash)` → `"none" | "creating" | "running"` — only looks at the pod, not the PVC. +- `listUserSessions(email, req)` — lists pods with managed-by label; only returns active/creating pods, **not** suspended sessions (PVC present, no pod). +- `updateLastActivity(hash)` — throttled patch of annotation on the pod. +- `deleteIdlePods()` — deletes pods idle > `IDLE_TIMEOUT_MINUTES`; PVC preserved. Runs every 60s. + +**`packages/opencode-router/src/api.ts`** + +- `GET /api/sessions` — lists sessions for user (calls `listUserSessions`). +- `POST /api/sessions` — creates PVC + Pod; returns `{ hash, url, state: "creating" }`. +- `GET /api/sessions/:hash` — returns pod state. +- **Missing**: no endpoint to suspend, terminate, or resume a session. + +**`packages/opencode-router-app/src/app.tsx`** + +- Sessions list only shows active/creating pods; no suspend/terminate/resume actions. + +**`deployment/homelab/src/index.ts`** + +- RBAC does not include `delete` verb on PVCs — needs updating to support termination. + +## Explore + + + +### Tasks + +_Tasks managed via `bd` CLI_ + +## Plan + + + +### Tasks + +_Tasks managed via `bd` CLI_ + +| ID | Title | +| --------------- | --------------------------------------------------------------------------------------- | +| opencode-1.2.1 | pod-manager: add stopped state + listUserSessions from PVCs | +| opencode-1.2.2 | pod-manager: add terminateSession (delete pod + PVC) | +| opencode-1.2.3 | pod-manager: add resumeSession (ensurePod for stopped session) | +| opencode-1.2.4 | pod-manager: add suggestBranch using human-id package | +| opencode-1.2.5 | api.ts: add DELETE /api/sessions/:hash (terminate) | +| opencode-1.2.6 | api.ts: add POST /api/sessions/:hash/resume | +| opencode-1.2.7 | api.ts: add GET /api/sessions/suggest-branch?repoUrl= | +| opencode-1.2.8 | api.ts: include lastActivity + idleTimeoutMinutes in session list and get | +| opencode-1.2.9 | config.ts: reduce default IDLE_TIMEOUT_MINUTES from 30 to 15 | +| opencode-1.2.10 | router-app api.ts: add stopped state, resume, terminate, suggestBranch | +| opencode-1.2.11 | router-app app.tsx: show stopped sessions, idle countdown, Resume and Terminate buttons | +| opencode-1.2.12 | router-app setup-form.tsx: pre-fill branch from suggest-branch endpoint | +| opencode-1.2.13 | deployment: add delete verb on PVCs to RBAC role | + +### Implementation Order & Dependencies + +``` +[1.2.9] config.ts default timeout + ↓ +[1.2.1] pod-manager: PVC-first list + stopped state ← foundation for everything + ├─→ [1.2.2] pod-manager: terminateSession + ├─→ [1.2.3] pod-manager: resumeSession + └─→ [1.2.4] pod-manager: suggestBranch (needs human-id package install first) + ↓ +[1.2.5] api: DELETE /api/sessions/:hash (depends on 1.2.2) +[1.2.6] api: POST /api/sessions/:hash/resume (depends on 1.2.3) +[1.2.7] api: GET /api/sessions/suggest-branch (depends on 1.2.4) +[1.2.8] api: lastActivity + idleTimeoutMinutes in responses (depends on 1.2.1) + ↓ +[1.2.10] router-app api.ts client types + callers (depends on 1.2.5/6/7/8) + ├─→ [1.2.11] router-app app.tsx UI + └─→ [1.2.12] router-app setup-form.tsx branch pre-fill +[1.2.13] deployment RBAC (independent, can land any time) +``` + +### Edge Cases & Risks + +| Risk | Mitigation | +| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| PVC delete while pod still running | k8s handles via Terminating finalizer; pod cleanup happens naturally | +| `listUserSessions` N+1 pod lookups | Batch: list all pods once, build hash→pod map, then correlate without extra API calls | +| `suggestBranch` loop infinite | Cap at 10 iterations; return last attempt regardless | +| Route ordering: `/suggest-branch` before `/:hash` | `suggest-branch` is not 12 hex chars — won't match hash regex; but keep route order explicit for clarity | +| Auth bypass on resume/terminate | Always re-validate email against PVC annotation server-side | +| `human-id` not yet installed | Add to `dependencies` in `packages/opencode-router/package.json` before implementing 1.2.4 | + +## Code + + + +### Tasks + +_Tasks managed via `bd` CLI — design details are in each task's description_ + +| ID | Title | +| --------------- | ------------------------------------------------------------------------- | +| opencode-1.3.1 | config.ts: reduce default IDLE_TIMEOUT_MINUTES from 30 to 15 | +| opencode-1.3.2 | pod-manager: stopped state + listUserSessions from PVCs | +| opencode-1.3.3 | pod-manager: add terminateSession (delete pod + PVC) | +| opencode-1.3.4 | pod-manager: add resumeSession (recreate pod for stopped session) | +| opencode-1.3.5 | pod-manager: add suggestBranch (collision-safe human-id name) | +| opencode-1.3.6 | api.ts: DELETE /api/sessions/:hash (terminate) | +| opencode-1.3.7 | api.ts: POST /api/sessions/:hash/resume | +| opencode-1.3.8 | api.ts: GET /api/sessions/suggest-branch?repoUrl= | +| opencode-1.3.9 | api.ts: include lastActivity + idleTimeoutMinutes in session list and get | +| opencode-1.3.10 | router-app api.ts: update types + add resume, terminate, suggestBranch | +| opencode-1.3.11 | router-app app.tsx: stopped sessions, idle countdown, Resume + Terminate | +| opencode-1.3.12 | router-app setup-form.tsx: auto-fill branch from suggest-branch endpoint | +| opencode-1.3.13 | deployment: add delete verb on PVCs to RBAC role | + +## Commit + + + +### Tasks + +_Tasks managed via `bd` CLI_ + +--- + +_This plan is maintained by the LLM and uses beads CLI for task management. Tool responses provide guidance on which bd commands to use for task management._ From 2e87b1a6bd53f22047d9de72bdfa2fddf5393aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Mon, 13 Apr 2026 09:34:36 +0200 Subject: [PATCH 009/616] chore(e2e): gitignore playwright-report and test-results artifacts --- packages/opencode-router-app/e2e/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 packages/opencode-router-app/e2e/.gitignore diff --git a/packages/opencode-router-app/e2e/.gitignore b/packages/opencode-router-app/e2e/.gitignore new file mode 100644 index 000000000000..5c4ffa21fa5e --- /dev/null +++ b/packages/opencode-router-app/e2e/.gitignore @@ -0,0 +1,2 @@ +playwright-report/ +test-results/ From dbb0c34d86ca6933896e387c0ffbd115fc9b8628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Mon, 13 Apr 2026 14:53:06 +0200 Subject: [PATCH 010/616] feat(deployment): pre-configure opencode session pods with baked image and config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Intent Pre-configure the opencode instance launched by the opencode-router with plugins, API keys, model settings, MCP servers, agent definitions, and skills — baking this configuration into a custom Docker image built via deployment/homelab/Makefile. # Major decisions - Custom opencode image extends upstream with dev tools (git, gh, bd, pnpm, python3) and globally pre-installs MCP packages to avoid registry calls at pod start - Config baked into /etc/opencode-defaults/ in image; seeded idempotently into the PVC-backed ~/.config/opencode/ by a merged init container on first pod start - Single 'init' container (replaces separate config-init + git-init) using the opencode image which already has git — removes one K8s container scheduling event - Init scripts (config/init-scripts/*.sh) run once after seeding; default script registers baked skills via npx @codemcp/skills experimental_install - Generic .env injection: operators set code:podEnv in Pulumi config to pass arbitrary env vars into session pods via the existing ConfigMap mount # Structural changes - deployment/homelab/images/opencode/ — new directory: Dockerfile, opencode.json, ADE agent, conventional-commits + adr-nygard skills, setup-skills.sh init script - deployment/homelab/Makefile — build-opencode / push-opencode targets; .dirty tag suffix when working tree is uncommitted (prevents silent no-op deploys) - deployment/homelab/src/index.ts — podEnv config var wired into ConfigMap .env key - packages/opencode-router/src/pod-manager.ts — merged init container; fix human-id named import { humanId } (default import fails under Node.js ESM / module:nodenext) - packages/opencode-router/tsconfig.json — exclude test files from tsc output --- .beads/issues.jsonl | 26 ++ .beads/last-touched | 2 +- .gitignore | 2 + .../development-plan-preconfigure-opencode.md | 386 ++++++++++++++++++ deployment/homelab/Makefile | 54 ++- deployment/homelab/Pulumi.dev.yaml | 6 +- deployment/homelab/images/opencode/Dockerfile | 40 ++ .../images/opencode/config/agents/ade.md | 115 ++++++ .../config/init-scripts/setup-skills.sh | 4 + .../images/opencode/config/opencode.json | 15 + .../config/skills/adr-nygard/SKILL.md | 45 ++ .../skills/conventional-commits/SKILL.md | 36 ++ deployment/homelab/src/index.ts | 7 + .../opencode-router/src/pod-manager.test.ts | 6 +- packages/opencode-router/src/pod-manager.ts | 62 +-- packages/opencode-router/tsconfig.json | 3 +- 16 files changed, 767 insertions(+), 42 deletions(-) create mode 100644 .vibe/development-plan-preconfigure-opencode.md create mode 100644 deployment/homelab/images/opencode/Dockerfile create mode 100644 deployment/homelab/images/opencode/config/agents/ade.md create mode 100644 deployment/homelab/images/opencode/config/init-scripts/setup-skills.sh create mode 100644 deployment/homelab/images/opencode/config/opencode.json create mode 100644 deployment/homelab/images/opencode/config/skills/adr-nygard/SKILL.md create mode 100644 deployment/homelab/images/opencode/config/skills/conventional-commits/SKILL.md diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 5fc335327d94..b4336dcb0117 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -35,3 +35,29 @@ {"id":"opencode-1.3.8","title":"api.ts: GET /api/sessions/suggest-branch?repoUrl=","description":"File: packages/opencode-router/src/api.ts\nDepends on: opencode-1.3.5\n\nImport suggestBranch from pod-manager.\n\nAdd route handler (place BEFORE /api/sessions/:hash handlers — belt-and-suspenders\neven though \"suggest-branch\" cannot match the 12-hex-char hash regex):\n Pattern: url === \"/api/sessions/suggest-branch\" + method GET\n Steps:\n 1. Parse repoUrl from query string: new URL(url, \"http://x\").searchParams.get(\"repoUrl\")\n 2. If !repoUrl → json(res, 400, { error: \"repoUrl is required\" })\n 3. Call suggestBranch(email, repoUrl)\n 4. Return json(res, 200, { branch })","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:46:02.025989+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:10:59.792293+02:00","closed_at":"2026-04-13T01:10:59.792293+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.8","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} {"id":"opencode-1.3.9","title":"api.ts: include lastActivity + idleTimeoutMinutes in session list and get","description":"File: packages/opencode-router/src/api.ts\nDepends on: opencode-1.3.2 (SessionInfo now has lastActivity + idleTimeoutMinutes)\n\nTwo changes:\n1. GET /api/sessions — no change needed: listUserSessions already returns the full\n SessionInfo array; the new fields flow through automatically.\n\n2. GET /api/sessions/:hash — currently returns { hash, state, url }.\n Change to also include lastActivity and idleTimeoutMinutes.\n Call getPodState(hash) is insufficient alone — need lastActivity too.\n Options:\n a) Add a new pod-manager helper getSessionInfo(hash, req) that returns full SessionInfo\n b) Keep getPodState but also call a new getLastActivity(hash) helper\n Prefer (a): add export getSessionInfo(hash: string, req): Promise\u003cSessionInfo | null\u003e\n to pod-manager that reads the pod (or PVC if pod absent) and returns full info.\n Return 404 if neither PVC nor pod exists.","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:46:10.91948+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T01:10:59.939602+02:00","closed_at":"2026-04-13T01:10:59.939602+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-1.3.9","depends_on_id":"opencode-1.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} {"id":"opencode-1.4","title":"Commit","description":"Ensure code quality and documentation accuracy through systematic cleanup and review. **STEP 1: Code Cleanup** Systematically clean up development artifacts: 1. **Remove Debug Output**: Search for and remove all temporary debug output statements used during development. Look for language-specific debug output methods (console logging, print statements, debug output functions). Remove any debugging statements that were added for development purposes. 2. **Review TODO/FIXME Comments**: - Address each TODO/FIXME comment by either implementing the solution or documenting why it's deferred - Remove completed TODOs - Convert remaining TODOs to proper issue tracking if needed 3. **Remove Debugging Code Blocks**: - Remove temporary debugging code, test code blocks, and commented-out code - Clean up any experimental code that's no longer needed - Ensure proper error handling replaces temporary debug logging **STEP 2: Documentation Review** Review and update documentation to reflect final implementation: 1. **Update Long-Term Memory Documents**: Based on what was actually implemented: - If exists: Update it if requirements changed during development - If exists: Update it if architectural impacts were identified - If exists: Update it if design details were refined or changed - Otherwise: Document any changes in the plan file 2. **Compare Against Implementation**: Review documentation against actual implemented functionality 3. **Update Changed Sections**: Only modify documentation sections that have functional changes 4. **Remove Development Progress**: Remove references to development iterations, progress notes, and temporary decisions 5. **Focus on Final State**: Ensure documentation describes the final implemented state, not the development process 6. **Ask User to Review Document Updates** **STEP 3: Final Validation** - Run existing tests to ensure cleanup didn't break functionality - Verify documentation accuracy with a final review - Ensure code is ready for production/delivery Update task progress and mark completed work as you finalize the feature.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T00:25:06.854241+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T00:25:06.854241+02:00","dependencies":[{"issue_id":"opencode-1.4","depends_on_id":"opencode-1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"opencode-1.4","depends_on_id":"opencode-1.3","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2","title":"opencode: epcc (development-plan-preconfigure-opencode.md)","description":"Responsible vibe engineering session using epcc workflow for opencode","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:20:50.855581+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:20:50.855581+02:00"} +{"id":"opencode-2.1","title":"Explore","description":"Research the codebase to understand existing patterns and gather context about the problem space. - If uncertain about conventions or rules, ask the user about them - Read relevant files and documentation - If exists: Understand and document requirements there - Otherwise: Document requirements in your task management system Focus on understanding without writing code yet. Document your findings and create tasks as needed.","status":"closed","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:20:51.02768+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:50:31.197605+02:00","closed_at":"2026-04-13T10:50:31.197605+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.1","depends_on_id":"opencode-2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.1.1","title":"Understand opencode config loading mechanism","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:29:16.880116+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:34:54.667743+02:00","closed_at":"2026-04-13T10:34:54.667743+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.1.1","depends_on_id":"opencode-2.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.1.2","title":"Analyze homelab opencode image pattern (commit 91405947)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:29:17.823464+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:34:54.796727+02:00","closed_at":"2026-04-13T10:34:54.796727+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.1.2","depends_on_id":"opencode-2.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.1.3","title":"Analyze current opencode-router pod-manager config injection","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:29:27.432856+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:34:54.915605+02:00","closed_at":"2026-04-13T10:34:54.915605+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.1.3","depends_on_id":"opencode-2.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.1.4","title":"Identify what needs to be baked into the opencode image","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:29:28.799035+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:34:55.01643+02:00","closed_at":"2026-04-13T10:34:55.01643+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.1.4","depends_on_id":"opencode-2.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.2","title":"Plan","description":"Create a detailed implementation strategy based on your exploration: - If exists: Base your strategy on requirements from it - Otherwise: Use existing task context Break down the work into specific, actionable tasks. Consider edge cases, dependencies, and potential challenges. - If architectural changes needed and exists: Document in - Otherwise: Create tasks to track architectural decisions - If exists: Adhere to the design in it - Otherwise: Elaborate design options and present them to the user Document the planning work thoroughly and create implementation tasks as part of the code phase as needed.","status":"closed","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:20:51.198755+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T11:38:09.990699+02:00","closed_at":"2026-04-13T10:50:31.326425+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.2","depends_on_id":"opencode-2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"opencode-2.2","depends_on_id":"opencode-2.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.2.1","title":"Design config layer architecture: image-baked defaults vs runtime injection vs operator ConfigMap","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:44:25.310968+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:47:14.500861+02:00","closed_at":"2026-04-13T10:47:14.500861+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.2.1","depends_on_id":"opencode-2.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.2.2","title":"Create deployment/homelab/images/opencode/ with Dockerfile and default config files","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:44:25.495042+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:49:41.470697+02:00","closed_at":"2026-04-13T10:49:41.470697+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.2.2","depends_on_id":"opencode-2.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.2.3","title":"Add build-opencode / push-opencode targets to Makefile","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:44:34.080223+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:49:41.614022+02:00","closed_at":"2026-04-13T10:49:41.614022+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.2.3","depends_on_id":"opencode-2.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.2.4","title":"Update Pulumi src/index.ts: opencodeImage variable + OPENCODE_CONFIG_DIR env","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:44:34.255731+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:49:41.748935+02:00","closed_at":"2026-04-13T10:49:41.748935+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.2.4","depends_on_id":"opencode-2.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.2.5","title":"Update Pulumi.dev.yaml and Makefile deploy target for new image","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:44:34.397935+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:49:41.869612+02:00","closed_at":"2026-04-13T10:49:41.869612+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.2.5","depends_on_id":"opencode-2.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.3","title":"Code","description":"Follow your plan to build the solution: - If exists: Follow the design from it - Otherwise: Elaborate design options and present them to the user - If exists: Build according to the architecture from it - Otherwise: Elaborate architectural options and present them to the user - If exists: Ensure requirements from it are met - Otherwise: Ensure existing requirements are met based on your task context Write clean, well-structured code with proper error handling. Prevent regression by building, linting, and executing existing tests. Stay flexible and adapt the plan as you learn more during implementation. Update task progress and create new tasks as needed.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:20:51.387083+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:20:51.387083+02:00","dependencies":[{"issue_id":"opencode-2.3","depends_on_id":"opencode-2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"opencode-2.3","depends_on_id":"opencode-2.2","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.3.1","title":"Create deployment/homelab/images/opencode/Dockerfile (extends upstream, COPYs config-defaults)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:50:01.406655+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T12:38:25.685212+02:00","closed_at":"2026-04-13T12:38:25.685212+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.1","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.3.10","title":"Pre-install MCP server packages into image — eliminate npx runtime downloads","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T12:18:05.316906+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T12:25:06.392204+02:00","closed_at":"2026-04-13T12:25:06.392204+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.10","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.3.11","title":"Review Dockerfile tools - verify apk/gh/bd installs match homelab reference","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T12:36:40.237198+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T12:37:13.718712+02:00","closed_at":"2026-04-13T12:37:13.718712+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.11","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.3.12","title":"Add init scripts support: run scripts after config-init seeds directory (e.g. npx @codemcp/skills install)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T12:36:45.058895+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T12:38:25.459197+02:00","closed_at":"2026-04-13T12:38:25.459197+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.12","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.3.2","title":"Create deployment/homelab/images/opencode/config/opencode.json (MCP servers, model, providers)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:50:01.592684+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T12:38:25.78416+02:00","closed_at":"2026-04-13T12:38:25.78416+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.2","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.3.3","title":"Create deployment/homelab/images/opencode/config/agents/*.md (example agent definitions)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:50:01.751158+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T12:38:25.89153+02:00","closed_at":"2026-04-13T12:38:25.89153+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.3","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.3.4","title":"Extend pod-manager.ts: config-init initContainer + emptyDir + XDG_CONFIG_HOME env","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:50:01.928167+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T11:07:47.078908+02:00","closed_at":"2026-04-13T11:07:47.078908+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.4","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.3.5","title":"Extend Makefile: build-opencode / push-opencode / include in build + push + deploy targets","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:50:02.098788+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T12:38:25.995088+02:00","closed_at":"2026-04-13T12:38:25.995088+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.5","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.3.6","title":"Extend Pulumi src/index.ts: opencodeImage config var already wired, verify no change needed","status":"closed","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:50:02.261393+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T12:38:26.097356+02:00","closed_at":"2026-04-13T12:38:26.097356+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.6","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.3.7","title":"Update Pulumi.dev.yaml: set code:opencodeImage to newly built custom tag","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:50:02.424386+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T14:50:36.211709+02:00","closed_at":"2026-04-13T14:50:36.211709+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.7","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.3.8","title":"Extend pod-manager.ts: add config-init initContainer (idempotent: seeds /home/opencode/.config/opencode from /etc/opencode-defaults only if not present)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T11:07:59.703187+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T12:38:25.57131+02:00","closed_at":"2026-04-13T12:38:25.57131+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.8","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.3.9","title":"Replace workflowAgents with .env file sourced from ConfigMap","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T11:56:37.4858+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T11:58:27.066402+02:00","closed_at":"2026-04-13T11:58:27.066402+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.9","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-2.4","title":"Commit","description":"Ensure code quality and documentation accuracy through systematic cleanup and review. **STEP 1: Code Cleanup** Systematically clean up development artifacts: 1. **Remove Debug Output**: Search for and remove all temporary debug output statements used during development. Look for language-specific debug output methods (console logging, print statements, debug output functions). Remove any debugging statements that were added for development purposes. 2. **Review TODO/FIXME Comments**: - Address each TODO/FIXME comment by either implementing the solution or documenting why it's deferred - Remove completed TODOs - Convert remaining TODOs to proper issue tracking if needed 3. **Remove Debugging Code Blocks**: - Remove temporary debugging code, test code blocks, and commented-out code - Clean up any experimental code that's no longer needed - Ensure proper error handling replaces temporary debug logging **STEP 2: Documentation Review** Review and update documentation to reflect final implementation: 1. **Update Long-Term Memory Documents**: Based on what was actually implemented: - If exists: Update it if requirements changed during development - If exists: Update it if architectural impacts were identified - If exists: Update it if design details were refined or changed - Otherwise: Document any changes in the plan file 2. **Compare Against Implementation**: Review documentation against actual implemented functionality 3. **Update Changed Sections**: Only modify documentation sections that have functional changes 4. **Remove Development Progress**: Remove references to development iterations, progress notes, and temporary decisions 5. **Focus on Final State**: Ensure documentation describes the final implemented state, not the development process 6. **Ask User to Review Document Updates** **STEP 3: Final Validation** - Run existing tests to ensure cleanup didn't break functionality - Verify documentation accuracy with a final review - Ensure code is ready for production/delivery Update task progress and mark completed work as you finalize the feature.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:20:51.579228+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:20:51.579228+02:00","dependencies":[{"issue_id":"opencode-2.4","depends_on_id":"opencode-2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"opencode-2.4","depends_on_id":"opencode-2.3","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]} diff --git a/.beads/last-touched b/.beads/last-touched index 69ef5d51785e..401299168e74 100644 --- a/.beads/last-touched +++ b/.beads/last-touched @@ -1 +1 @@ -opencode-1.3.15 +opencode-2.3.12 diff --git a/.gitignore b/.gitignore index 52a5a0459626..82c46a9b13fa 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ UPCOMING_CHANGELOG.md logs/ *.bun-build tsconfig.tsbuildinfo +.playwright-mcp +e2e*.png diff --git a/.vibe/development-plan-preconfigure-opencode.md b/.vibe/development-plan-preconfigure-opencode.md new file mode 100644 index 000000000000..dd902b9b87aa --- /dev/null +++ b/.vibe/development-plan-preconfigure-opencode.md @@ -0,0 +1,386 @@ +# Development Plan: opencode (preconfigure-opencode branch) + +_Generated on 2026-04-13 by Vibe Feature MCP_ +_Workflow: [epcc](https://mrsimpson.github.io/responsible-vibe-mcp/workflows/epcc)_ + +## Goal + +Pre-configure the opencode instance launched by the **opencode-router** with plugins, API keys, model settings, MCP servers, and agent definitions — baking this configuration into the custom Docker image built from `./deployment/homelab/Makefile`. + +The approach mirrors what was done in the homelab repo (commit `91405947`): pack a config directory into the image and let opencode discover it via its XDG config path resolution. + +## Key Decisions + +### KD-1: Config injection strategy — bake into image vs. Kubernetes ConfigMap + +The homelab approach uses a Kubernetes ConfigMap + init container to restore a config directory tree into an emptyDir. For the opencode-router context, baking the config directly into a custom Docker image is simpler: no init container needed, no Kubernetes resources to manage. + +The config is written to `/etc/opencode/` (Linux system-managed config path) which opencode reads with **highest** priority. This avoids touching the user home dir and survives PVC mounts. + +### KD-2: API keys via environment variables (NOT baked in) + +API keys (`ANTHROPIC_API_KEY`, etc.) must **not** be baked into the image. They are injected at runtime via Kubernetes Secrets → `envFrom: secretRef`. The `OPENCODE_CONFIG_CONTENT` env var or the system-level `opencode.json` can reference `{env:ANTHROPIC_API_KEY}` for the model/provider config, or the keys are simply present as env vars which opencode auto-detects. + +### KD-3: opencode config resolution order (key insight) + +opencode merges configs from multiple sources. Highest priority sources: + +1. `OPENCODE_CONFIG_CONTENT` env var (inline JSON) ← great for runtime injection +2. System-managed files: Linux → `/etc/opencode/opencode.json{c}` ← great for image-baked config +3. Custom directory: `OPENCODE_CONFIG_DIR` env var +4. `~/.config/opencode/opencode.json` (global, via XDG) +5. Project-level `opencode.json` (workspace root) + +**Decision**: Bake config into `/etc/opencode/opencode.json` inside the image. This makes it "system defaults" that any per-session or per-project config can still override. + +### KD-4: What to bake vs. what to inject at runtime + +**Bake into image** (stable, non-secret): + +- MCP server definitions (workflows, knowledge, agentskills, etc.) +- Default model settings (but not API keys) +- Agent definitions (`.opencode/agents/*.md` files) +- Plugin configuration +- Permission defaults + +**Inject at runtime** (secrets / environment-specific): + +- API keys (`ANTHROPIC_API_KEY`, etc.) via Kubernetes Secret → `envFrom` +- Model override (in case the deployment wants a specific model) + +### KD-5: Config approach — use standard home dir (XDG default), seed PVC on first start + +**Question asked**: Would it be simpler to use the home dir (`Global.Path.config` = `~/.config/opencode`) for all config rather than a custom `XDG_CONFIG_HOME` or `OPENCODE_CONFIG_DIR`? + +**Answer: Yes — this is the simplest correct approach**, given that: + +- The homelab opencode image already sets `USER opencode` and `HOME=/home/opencode` +- The pod mounts the PVC directly at `/home/opencode` (the user's home dir) +- `Global.Path.config` = `$XDG_CONFIG_HOME/opencode` = `~/.config/opencode` = `/home/opencode/.config/opencode` — which lives **on the PVC** + +**Why not bake config into `/home/opencode/.config/opencode/` in the image layer?** + +The PVC is mounted at `/home/opencode` — the mount **hides the image layer** at that path entirely. Files COPY'd to `/home/opencode/...` in the Dockerfile are unreachable at runtime. The init container is therefore still needed, but it seeds the **PVC itself** (which persists across pod restarts). + +**Why not `XDG_CONFIG_HOME` pointing to a separate emptyDir (previous plan)?** + +It works, but it's more complex than necessary: an extra env var, an extra volume, and the config is reset on every pod restart (emptyDir is ephemeral). The PVC is already there and persistent — use it. + +**Why not `OPENCODE_CONFIG_DIR=/etc/opencode-defaults` (read-only, no init container)?** + +`installDependencies()` in config.ts is guarded by `isWritable()` and returns early for read-only dirs — so read-only is technically safe. However, it means users cannot edit agents or add plugins at runtime (everything is frozen in the image). For flexibility it's inferior. + +**Chosen approach: image-baked defaults → init container → PVC-backed `~/.config/opencode/`** + +1. Custom Docker image bakes all config under `/etc/opencode-defaults/`: + - `opencode.json` — MCP servers, model, providers, permissions + - `agents/*.md` — agent definitions + - `commands/*.md` — slash commands +2. The **`config-init` init container** (reusing the same image) checks if `/home/opencode/.config/opencode` already exists on the PVC; if not, it copies from `/etc/opencode-defaults/`. This is idempotent — subsequent pod restarts skip the copy. +3. No `XDG_CONFIG_HOME` env var needed. `HOME=/home/opencode` (already set in the image) → `Global.Path.config = /home/opencode/.config/opencode` — writable, on the PVC, discovered automatically. + +**Priority order in the running pod (lowest → highest):** + +1. Project `opencode.json` in workspace +2. `.opencode/` dirs in workspace and `$HOME` +3. **`/home/opencode/.config/opencode/` (XDG global — image defaults, seeded on first start)** +4. K8s ConfigMap at `/home/opencode/.opencode/` (operator override, read-only mount) +5. `OPENCODE_CONFIG_CONTENT` env var (runtime override) +6. `/etc/opencode/opencode.json` (system-managed — highest, for hard operator overrides) + +**For forks**: edit files under `deployment/homelab/images/opencode/config/`. One directory, all config types (opencode.json + agents/ + commands/). Rebuild the image. On next fresh session pod, the PVC is empty and the init container seeds the new defaults. + +**KD-5b: Init container idempotency** + +```sh +if [ ! -d /home/opencode/.config/opencode ]; then + cp -r /etc/opencode-defaults/. /home/opencode/.config/opencode/ +fi +``` + +- **First pod start** (new session, empty PVC): directory doesn't exist → copy runs +- **Pod restart** (same session, PVC still has data): directory exists → copy skipped, user's modifications preserved +- **Image update** (fork updates config): new sessions get new defaults; existing sessions keep their PVC copy (intentional — users may have customised) + +**KD-5c: Three options considered** + +| Option | Init container needed | `XDG_CONFIG_HOME` env var | Config persists across restarts | User can edit config at runtime | +| --------------------------------- | --------------------- | ------------------------- | ------------------------------- | ------------------------------- | +| A. emptyDir + `XDG_CONFIG_HOME` | Yes | Yes | No (emptyDir) | No | +| **B. PVC home dir (chosen)** | **Yes (idempotent)** | **No** | **Yes (PVC)** | **Yes** | +| C. `OPENCODE_CONFIG_DIR=/etc/...` | No | No | N/A (read-only) | No | + +### KD-6: Config content — reference from homelab commit 91405947 with plugin substitutions + +Based on `packages/apps/opencode/config/` from homelab commit `91405947`: + +**Plugins** (replaces the `workflows` MCP server from the reference): + +```json +"plugin": [ + "@ex-machina/opencode-anthropic-auth@1.5.1", + "@codemcp/workflows-opencode@6.11.1" +] +``` + +- `@ex-machina/opencode-anthropic-auth` — Anthropic OAuth auth so Claude Pro/Max users can use their subscription +- `@codemcp/workflows-opencode` — structured development workflow plugin (replaces `@codemcp/workflows-server` MCP) + +**MCP servers kept** (from `.mcp.json` in homelab commit): + +- `knowledge` → `@codemcp/knowledge-server` (npx) +- `agentskills` → `@codemcp/skills-server` (npx) +- `workflows` MCP server **removed** (replaced by plugin above) + +**Agent** (from `.opencode/agents/ade.md`): the ADE agent definition with full permission policy + +**Skills** (from `.agentskills/skills/` and `.ade/skills/`): + +- `conventional-commits` skill +- `adr-nygard` skill + +**Image base**: `ghcr.io/anomalyco/opencode` (upstream, no tag pin — homelab variant adds tools on top) + +**Dockerfile tools** (from `images/opencode/Dockerfile` in homelab commit): + +- `git, curl, bash, nodejs, npm, pnpm, python3` via apk +- `gh` CLI (GitHub CLI) +- `bd` (beads task manager) +- Non-root user `opencode` (UID 1000), `HOME=/home/opencode` + +### KD-7: Current opencode-router ConfigMap approach + +The current `pod-manager.ts` already mounts a ConfigMap at `/home/opencode/.opencode` (line 249): + +```ts +{ name: "opencode-config", mountPath: "/home/opencode/.opencode", readOnly: true } +``` + +The ConfigMap (`opencode-config-dir`) contains `opencode.json` with the current model. This is the injection point. We can either: + +- **Extend the ConfigMap** with more files (agents, MCP config) +- **Bake into image** (simpler, no K8s resource changes needed for config updates) + +Both can coexist. The image provides system defaults; the ConfigMap overrides specific fields. + +### KD-9: Generic env injection via `.env` file in ConfigMap (replaces KD-8) + +Rather than adding named env var pass-throughs to the router (e.g. `workflowAgents`), the operator populates a `.env` file inside the **existing ConfigMap** (`opencode-config-dir`) which is already mounted at `/home/opencode/.opencode/`. + +The opencode container entrypoint sources it before exec'ing the server: + +```sh +set -a; . /home/opencode/.opencode/.env 2>/dev/null || true; set +a; exec opencode serve ... +``` + +**Pulumi side**: `cfg.get("podEnv")` reads an optional multiline string from `code:podEnv` config key. Example: + +```yaml +code:podEnv: | + WORKFLOW_AGENTS=ade + OPENCODE_MODEL=anthropic/claude-opus-4 +``` + +**Benefits over named pass-throughs**: + +- Zero router code changes needed for new vars +- Standard `.env` syntax, operator-friendly +- ConfigMap already mounted — no new volumes or secrets +- Works for any current or future env var opencode supports + +**`workflowAgents` Pulumi config key and `config.workflowAgents` removed** — subsumed by this generic mechanism. + +### KD-12: Merged init containers — single `init` container using the opencode image + +The original design used two sequential init containers (`config-init` + `git-init`). Each container start costs ~1–3s of K8s scheduling overhead, so two containers meant paying that cost twice. + +**Decision: merge into a single `init` container** using the custom opencode image (which already has `git` via `apk add`). The merged script runs config seeding first, then the git clone/checkout: + +```sh +set -e +# --- config phase (idempotent) --- +if [ ! -d /home/opencode/.config/opencode ]; then + mkdir -p /home/opencode/.config/opencode + cp -r /etc/opencode-defaults/. /home/opencode/.config/opencode/ + for s in /etc/opencode-defaults/init-scripts/*.sh; do + [ -f "$s" ] && sh "$s" || true + done +fi +# --- git phase --- +GIT="git -c safe.directory=/workspace" +if [ ! -d /workspace/.git ]; then + git clone "" /workspace +fi +... +``` + +**Why merging into the opencode image (not alpine/git) is the right direction:** + +- The opencode image is already pulled for the main container — no extra pull cost +- `alpine/git` does not have `/etc/opencode-defaults/`, so config seeding cannot move there +- Single init container = one scheduling event instead of two +- `HOME` writable concern goes away: `HOME=/home/opencode` on the PVC is available to the merged script, and `safe.directory` still bypasses the global git config write + +**Volume mounts on the merged container:** + +- `/home/opencode` ← full PVC (for config seeding + HOME) +- `/workspace` ← PVC subPath `projects` (for git clone) + +### KD-11: Init scripts — part of config tree, run by config-init after seeding + +Init scripts live under `config/init-scripts/*.sh` — baked into `/etc/opencode-defaults/init-scripts/` via the existing `COPY config/ /etc/opencode-defaults/` Dockerfile instruction. They are **not** hardcoded into the Dockerfile itself. + +The `config-init` container runs them **once**, after seeding the config directory on first pod start: + +```sh +if [ ! -d /home/opencode/.config/opencode ]; then + mkdir -p /home/opencode/.config/opencode + cp -r /etc/opencode-defaults/. /home/opencode/.config/opencode/ + for s in /etc/opencode-defaults/init-scripts/*.sh; do + [ -f "$s" ] && sh "$s" || true + done +fi +``` + +- Scripts are skipped on pod restart (guarded by the same `if [ ! -d ... ]` check) +- Scripts run with `HOME=/home/opencode` pointing at the PVC — needed for tools that write to `~` +- `|| true` ensures a failing script doesn't block pod startup +- Adding new init behavior = add a `.sh` file under `config/init-scripts/`, rebuild image + +**Default script**: `config/init-scripts/post-init.sh` runs `npx -y @codemcp/skills install` to register baked-in skills with `@codemcp/skills-server`. + +### KD-10: MCP packages pre-installed globally — `npx -y` config unchanged + +`@codemcp/skills-server` fails at pod start because its dist bundle imports `gray-matter`, `ajv`, and `zod` as bare specifiers that must resolve from `node_modules`. Unlike `@codemcp/knowledge-server` (which only needs Node built-ins + MCP SDK), `skills-server` ships with `pacote@21.3.1` as a runtime dep — a heavy subtree that makes `npx` installs slow and unreliable in restricted pod environments. + +**Fix**: `npm install -g` both MCP packages in the Dockerfile, run as root during image build. Globally installed packages are found by `npx` before it tries the registry, so **`opencode.json` stays unchanged** with `["npx", "-y", "@codemcp/skills-server"]`. + +**Self-documenting Dockerfile**: rather than hardcoding package names, a `node -e` inline script reads `opencode.json` at build time and installs every `npx`-referenced MCP command globally. Adding a new MCP server to `opencode.json` automatically gets it pre-installed — no Dockerfile edit needed. + +**Layer ordering**: `COPY config/` happens before the `npm install -g` RUN step so the inline script can parse `opencode.json`. The config copy is root-owned (no `--chown`) since `/etc/opencode-defaults/` is intentionally read-only. + +## Notes + +### opencode Configuration System (as-explored) + +**`Global.Path.config`** = `$XDG_CONFIG_HOME/opencode` (default: `~/.config/opencode`) + +The `ConfigPaths.directories()` function returns, in order: + +1. `Global.Path.config` — always included; agents/commands/plugins are scanned here +2. `.opencode/` dirs walked up from the workspace root +3. `.opencode/` in `$HOME` +4. `OPENCODE_CONFIG_DIR` env var if set (also triggers `opencode.json` read inside it) + +The main `opencode.json` at `Global.Path.config` is loaded via `loadGlobal()` (separate from the directory scan). **All** directories in the list also get `loadAgent()`, `loadCommand()`, `loadPlugin()` called on them — scanning for markdown files in `agents/`, `commands/`, `plugins/` subdirs. + +**Critical constraint**: `global/index.ts` calls `fs.mkdir(Global.Path.config, { recursive: true })` at startup → the path must be writable, so `/etc/opencode` cannot be used directly as `XDG_CONFIG_HOME`. + +**Config priority order (lowest → highest)**: + +1. Well-known remote fetch +2. `~/.config/opencode/opencode.json` (XDG global) +3. `.opencode/` dirs in project + home +4. `OPENCODE_CONFIG_DIR` env var +5. `OPENCODE_CONFIG_CONTENT` env var +6. Console/account remote config +7. System-managed: `/etc/opencode/opencode.json{c}` (read-only override) +8. macOS MDM (not relevant) + +### Implementation Strategy + +**Chosen design: image-baked defaults → idempotent init container → PVC-backed `~/.config/opencode/`** + +The homelab opencode image already has `USER opencode` / `HOME=/home/opencode`. The pod PVC mounts at `/home/opencode`. So `Global.Path.config` = `/home/opencode/.config/opencode` — **already on the PVC, writable, no extra env vars needed**. + +``` +Docker image + └── /etc/opencode-defaults/ ← read-only, baked at build time + ├── opencode.json ← MCP servers, model, providers, permissions + ├── agents/ + │ └── researcher.md ← example — forks add their own here + └── commands/ + └── review.md + +Session pod spec + initContainers: + - name: config-init + image: ← same image, reuses /etc/opencode-defaults + command: ["sh", "-c"] + args: + - | + if [ ! -d /home/opencode/.config/opencode ]; then + mkdir -p /home/opencode/.config/opencode + cp -r /etc/opencode-defaults/. /home/opencode/.config/opencode/ + fi + volumeMounts: + - name: user-data + mountPath: /home/opencode + containers: + - name: opencode + # No XDG_CONFIG_HOME needed — HOME=/home/opencode already set in image + volumeMounts: + - name: user-data ← PVC → /home/opencode (includes .config/opencode) + mountPath: /home/opencode + - name: opencode-config ← K8s ConfigMap override (higher priority) + mountPath: /home/opencode/.opencode + readOnly: true +``` + +**No extra env var needed.** `HOME=/home/opencode` is baked into the image → `Global.Path.config = /home/opencode/.config/opencode` is discovered automatically by opencode. + +**Persistence**: config lives on the PVC — survives pod restarts. On new sessions (fresh PVC) the init container seeds the defaults once. + +**For forks**: edit `deployment/homelab/images/opencode/config/`. One directory, all config. Rebuild image. New sessions get new defaults; existing sessions keep their PVC copy. + +### Files to Create / Change + +| File | Action | Purpose | +| --------------------------------------------------------- | ---------- | -------------------------------------------------------------------------------------- | +| `deployment/homelab/images/opencode/Dockerfile` | **Create** | Extends upstream, COPYs config to `/etc/opencode-defaults/` | +| `deployment/homelab/images/opencode/config/opencode.json` | **Create** | Default MCP/model config | +| `deployment/homelab/images/opencode/config/agents/*.md` | **Create** | Example agent definitions | +| `deployment/homelab/Makefile` | **Extend** | `build-opencode` / `push-opencode` targets | +| `packages/opencode-router/src/pod-manager.ts` | **Extend** | Add `config-init` initContainer (idempotent seed); **no** emptyDir, **no** XDG env var | +| `deployment/homelab/src/index.ts` | **Verify** | `opencodeImage` Pulumi config var already present | +| `deployment/homelab/Pulumi.dev.yaml` | **Extend** | Set `code:opencodeImage` to new custom tag | + +## Explore + + + +### Tasks + + + +_Auto-synced — do not edit here, use `bd` CLI instead._ + +## Plan + + + +### Tasks + + + +_Auto-synced — do not edit here, use `bd` CLI instead._ + +## Code + + + +### Tasks + + + +_Auto-synced — do not edit here, use `bd` CLI instead._ + +## Commit + + + +### Tasks + + + +_Auto-synced — do not edit here, use `bd` CLI instead._ diff --git a/deployment/homelab/Makefile b/deployment/homelab/Makefile index d267b2a8eef8..5a864158ba8e 100644 --- a/deployment/homelab/Makefile +++ b/deployment/homelab/Makefile @@ -39,22 +39,27 @@ MONOREPO_ROOT := $(DEPLOY_DIR)/.. # Derived image names ROUTER_IMAGE := ghcr.io/$(GHCR_USER)/opencode-router OPERATOR_IMAGE := ghcr.io/$(GHCR_USER)/opencode-cloudflare-operator +OPENCODE_IMAGE := ghcr.io/$(GHCR_USER)/opencode # Versions read from package.json files ROUTER_VERSION := $(shell node -p "require('$(MONOREPO_ROOT)/packages/opencode-router/package.json').version" 2>/dev/null) OPERATOR_VERSION := $(shell node -p "require('$(DEPLOY_DIR)/opencode-cloudflare-operator/package.json').version" 2>/dev/null) +OPENCODE_VERSION := $(shell node -p "require('$(MONOREPO_ROOT)/packages/opencode/package.json').version" 2>/dev/null) -# Git short SHA for uniqueness +# Git short SHA for uniqueness — append .dirty when working tree has uncommitted changes SHA_SHORT := $(shell git -C $(MONOREPO_ROOT) rev-parse --short HEAD 2>/dev/null || echo "local") +GIT_DIRTY := $(shell git -C $(MONOREPO_ROOT) status --porcelain 2>/dev/null | grep -q . && echo ".dirty" || echo "") -# Final image tags (matches CI pattern: -.) -ROUTER_TAG := $(ROUTER_VERSION)-local.$(SHA_SHORT) -OPERATOR_TAG := $(OPERATOR_VERSION)-local.$(SHA_SHORT) +# Final image tags (matches CI pattern: -local.[.dirty]) +ROUTER_TAG := $(ROUTER_VERSION)-local.$(SHA_SHORT)$(GIT_DIRTY) +OPERATOR_TAG := $(OPERATOR_VERSION)-local.$(SHA_SHORT)$(GIT_DIRTY) +OPENCODE_TAG := $(OPENCODE_VERSION)-local.$(SHA_SHORT)$(GIT_DIRTY) ROUTER_FULL := $(ROUTER_IMAGE):$(ROUTER_TAG) OPERATOR_FULL := $(OPERATOR_IMAGE):$(OPERATOR_TAG) +OPENCODE_FULL := $(OPENCODE_IMAGE):$(OPENCODE_TAG) -.PHONY: help build-router build-operator build push-router push-operator push build-push deploy release preview destroy logs +.PHONY: help build-router build-operator build-opencode build push-router push-operator push-opencode push build-push deploy release preview destroy logs # Default target help: @@ -64,11 +69,13 @@ help: @echo "Image build targets:" @echo " build-router Build opencode-router image (linux/amd64)" @echo " build-operator Build opencode-cloudflare-operator image (linux/amd64)" - @echo " build Build both images" + @echo " build-opencode Build custom opencode image (linux/amd64)" + @echo " build Build all images" @echo " push-router Push router image to GHCR (requires GITHUB_PAT)" @echo " push-operator Push operator image to GHCR (requires GITHUB_PAT)" - @echo " push Push both images" - @echo " build-push Build + push both images" + @echo " push-opencode Push opencode image to GHCR (requires GITHUB_PAT)" + @echo " push Push all images" + @echo " build-push Build + push all images" @echo "" @echo "Deployment targets:" @echo " preview Pulumi preview (dry-run)" @@ -82,6 +89,7 @@ help: @echo " GHCR user : $(GHCR_USER)" @echo " Router : $(ROUTER_FULL)" @echo " Operator : $(OPERATOR_FULL)" + @echo " Opencode : $(OPENCODE_FULL)" @echo "" # ------------------------------------------------------------------------------ @@ -112,7 +120,19 @@ build-operator: $(DEPLOY_DIR)/opencode-cloudflare-operator @echo "✓ Built $(OPERATOR_FULL)" -build: build-router build-operator +# Opencode: custom image baking default config — build context is images/opencode/ +build-opencode: + @echo "→ Building $(OPENCODE_FULL) ..." + docker buildx build \ + --platform linux/amd64 \ + --file $(PULUMI_DIR)/images/opencode/Dockerfile \ + --tag $(OPENCODE_FULL) \ + --tag $(OPENCODE_IMAGE):latest \ + --load \ + $(PULUMI_DIR)/images/opencode + @echo "✓ Built $(OPENCODE_FULL)" + +build: build-router build-operator build-opencode # ------------------------------------------------------------------------------ # Push — requires GITHUB_PAT with write:packages scope @@ -148,7 +168,18 @@ push-operator: _ghcr-login $(DEPLOY_DIR)/opencode-cloudflare-operator @echo "✓ Pushed $(OPERATOR_FULL)" -push: push-router push-operator +push-opencode: _ghcr-login + @echo "→ Pushing $(OPENCODE_FULL) ..." + docker buildx build \ + --platform linux/amd64 \ + --file $(PULUMI_DIR)/images/opencode/Dockerfile \ + --tag $(OPENCODE_FULL) \ + --tag $(OPENCODE_IMAGE):latest \ + --push \ + $(PULUMI_DIR)/images/opencode + @echo "✓ Pushed $(OPENCODE_FULL)" + +push: push-router push-operator push-opencode build-push: build push @@ -165,7 +196,8 @@ deploy: @echo "→ Updating Pulumi image config ..." cd $(PULUMI_DIR) && \ pulumi config set --stack $(STACK_NAME) code:routerImage "$(ROUTER_FULL)" && \ - pulumi config set --stack $(STACK_NAME) code:cfOperatorImage "$(OPERATOR_FULL)" + pulumi config set --stack $(STACK_NAME) code:cfOperatorImage "$(OPERATOR_FULL)" && \ + pulumi config set --stack $(STACK_NAME) code:opencodeImage "$(OPENCODE_FULL)" @echo "→ Deploying stack $(STACK_NAME) ..." cd $(PULUMI_DIR) && pulumi up --stack $(STACK_NAME) --yes @echo "✓ Deployed" diff --git a/deployment/homelab/Pulumi.dev.yaml b/deployment/homelab/Pulumi.dev.yaml index 9398d8fb26f9..80ff00cc90f2 100644 --- a/deployment/homelab/Pulumi.dev.yaml +++ b/deployment/homelab/Pulumi.dev.yaml @@ -1,7 +1,7 @@ config: - code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-local.af5c3ddb2 - code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-local.af5c3ddb2 - code:opencodeImage: ghcr.io/mrsimpson/opencode:1.2.27-homelab.6 + code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-local.2e87b1a6b.dirty + code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-local.2e87b1a6b.dirty + code:opencodeImage: ghcr.io/mrsimpson/opencode:1.3.13-local.2e87b1a6b.dirty code:homelabStack: mrsimpson/homelab/dev code:anthropicApiKey: secure: AAABAG/9eXalNNef/hxPr5yv8F9phJnv6c/p7GVpY8Rnutzox59WLEkwutX7RtF7djYUqqXHf7jRoR7PYDkf0Kx1dr8OF+coy/u3i/S+Cz3DpQzfwQrhRCyx/AtSC3AIUiteAfzlxK/UDAse2MabbOuk8W733qZVhFuBDkqxYgm3Du9kFZD97anwz1Y= diff --git a/deployment/homelab/images/opencode/Dockerfile b/deployment/homelab/images/opencode/Dockerfile new file mode 100644 index 000000000000..3b017377227e --- /dev/null +++ b/deployment/homelab/images/opencode/Dockerfile @@ -0,0 +1,40 @@ +FROM ghcr.io/anomalyco/opencode + +# Install tools as root before switching to the non-root user: +# git, curl, bash — essential dev tools +# nodejs, npm — Node.js LTS + npm (latest from Alpine apk) +# pnpm — fast Node package manager +# python3 — Python interpreter (for MCP servers, scripts) +# gh — GitHub CLI (linux/amd64 tarball from GitHub releases) +# bd — beads task-management CLI (https://github.com/steveyegge/beads) + +RUN apk add --no-cache git curl bash nodejs npm pnpm python3 && \ + # gh CLI — resolve latest version tag, fetch and extract just the binary + GH_VERSION=$(curl -fsSL https://api.github.com/repos/cli/cli/releases/latest \ + | grep '"tag_name"' | head -1 | sed 's/.*"v\([^"]*\)".*/\1/') && \ + curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" \ + | tar -xz -C /usr/local/bin --strip-components=2 "gh_${GH_VERSION}_linux_amd64/bin/gh" && \ + # bd (beads) — installs directly to /usr/local/bin/bd + curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash && \ + # Ensure all installed binaries are owned by root and world-executable + chown root:root /usr/local/bin/bd /usr/local/bin/beads /usr/local/bin/gh && \ + chmod 755 /usr/local/bin/bd /usr/local/bin/beads /usr/local/bin/gh + +# Bake default config into a read-only system path. +# The config-init init container seeds /home/opencode/.config/opencode/ from +# this directory on first pod start (idempotent — skipped on restart). +COPY config/ /etc/opencode-defaults/ + +# Pre-install MCP servers globally so `npx -y` resolves them from cache, never the registry. +# Keep this list in sync with the `mcp` commands in config/opencode.json. +RUN npm install -g \ + @codemcp/knowledge-server \ + @codemcp/skills-server + +# Non-root user matching the opencode-router pod security context (UID 1000). +RUN addgroup -S opencode && \ + adduser -S -u 1000 -G opencode -h /home/opencode -s /bin/sh opencode && \ + chown -R opencode:opencode /home/opencode + +USER opencode +ENV HOME=/home/opencode diff --git a/deployment/homelab/images/opencode/config/agents/ade.md b/deployment/homelab/images/opencode/config/agents/ade.md new file mode 100644 index 000000000000..36c27b596963 --- /dev/null +++ b/deployment/homelab/images/opencode/config/agents/ade.md @@ -0,0 +1,115 @@ +--- +name: ade +description: ADE — Agentic Development Environment agent with project conventions and tools +permission: + start_development: "ask" + proceed_to_phase: "ask" + whats_next: "allow" + conduct_review: "allow" + list_workflows: "allow" + "knowledge*": "allow" + "agentskills*": "allow" + read: + "*": "allow" + "*.env": "deny" + "*.env.*": "deny" + "*.env.example": "allow" + skill: "deny" + todoread: "deny" + todowrite: "deny" + task: "allow" + lsp: "allow" + glob: "allow" + grep: "allow" + list: "allow" + external_directory: "ask" + edit: "allow" + webfetch: "ask" + websearch: "ask" + codesearch: "ask" + bash: + "*": "ask" + "grep *": "allow" + "rg *": "allow" + "find *": "allow" + "fd *": "allow" + "ls *": "allow" + "cat *": "allow" + "head *": "allow" + "tail *": "allow" + "wc *": "allow" + "sort *": "allow" + "uniq *": "allow" + "diff *": "allow" + "echo *": "allow" + "printf *": "allow" + pwd: "allow" + "which *": "allow" + "type *": "allow" + whoami: "allow" + date: "allow" + "date *": "allow" + env: "allow" + "tree *": "allow" + "file *": "allow" + "stat *": "allow" + "readlink *": "allow" + "realpath *": "allow" + "dirname *": "allow" + "basename *": "allow" + "sed *": "allow" + "awk *": "allow" + "cut *": "allow" + "tr *": "allow" + "tee *": "allow" + "xargs *": "allow" + "jq *": "allow" + "yq *": "allow" + "mkdir *": "allow" + "touch *": "allow" + "kill *": "ask" + "rm *": "deny" + "rmdir *": "deny" + "curl *": "deny" + "wget *": "deny" + "chmod *": "deny" + "chown *": "deny" + "sudo *": "deny" + "su *": "deny" + "sh *": "deny" + "bash *": "deny" + "zsh *": "deny" + "eval *": "deny" + "exec *": "deny" + "source *": "deny" + ". *": "deny" + "nohup *": "deny" + "dd *": "deny" + "mkfs *": "deny" + "mount *": "deny" + "umount *": "deny" + "killall *": "deny" + "pkill *": "deny" + "nc *": "deny" + "ncat *": "deny" + "ssh *": "deny" + "scp *": "deny" + "rsync *": "deny" + "docker *": "deny" + "kubectl *": "deny" + "systemctl *": "deny" + "service *": "deny" + "crontab *": "deny" + reboot: "deny" + "shutdown *": "deny" + "passwd *": "deny" + "useradd *": "deny" + "userdel *": "deny" + "iptables *": "deny" +--- + +You are an AI assistant that helps users develop software features using the workflows server. +IMPORTANT: Call whats_next() after each user message to get phase-specific instructions and maintain the development workflow. +Each tool call returns a JSON response with an "instructions" field. Follow these instructions immediately after you receive them. +Use the development plan which you will retrieve via whats_next() to record important insights and decisions as per the structure of the plan. +Do not use your own task management tools. diff --git a/deployment/homelab/images/opencode/config/init-scripts/setup-skills.sh b/deployment/homelab/images/opencode/config/init-scripts/setup-skills.sh new file mode 100644 index 000000000000..05cb368df08a --- /dev/null +++ b/deployment/homelab/images/opencode/config/init-scripts/setup-skills.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# Post-init: register skills from ~/.config/opencode/skills/ with @codemcp/skills-server. +# Runs once after config-init seeds the config directory on first pod start. +npx -y @codemcp/skills experimental_install diff --git a/deployment/homelab/images/opencode/config/opencode.json b/deployment/homelab/images/opencode/config/opencode.json new file mode 100644 index 000000000000..faf387c90d29 --- /dev/null +++ b/deployment/homelab/images/opencode/config/opencode.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://opencode.ai/config.json", + "model": "anthropic/claude-sonnet-4-5", + "plugin": ["@ex-machina/opencode-anthropic-auth@1.5.1", "@codemcp/workflows-opencode@6.11.1"], + "mcp": { + "knowledge": { + "type": "local", + "command": ["npx", "-y", "@codemcp/knowledge-server"] + }, + "agentskills": { + "type": "local", + "command": ["npx", "-y", "@codemcp/skills-server"] + } + } +} diff --git a/deployment/homelab/images/opencode/config/skills/adr-nygard/SKILL.md b/deployment/homelab/images/opencode/config/skills/adr-nygard/SKILL.md new file mode 100644 index 000000000000..557563013665 --- /dev/null +++ b/deployment/homelab/images/opencode/config/skills/adr-nygard/SKILL.md @@ -0,0 +1,45 @@ +--- +name: adr-nygard +description: Architecture Decision Records following Nygard's lightweight template +--- + +# Architecture Decision Records (Nygard) + +## When to Write an ADR + +- When making a significant architectural decision +- When choosing between multiple viable options +- When the decision will be hard to reverse +- When future developers will ask "why did we do this?" + +## Template + +Store ADRs in `docs/adr/` as numbered markdown files: `NNNN-title-with-dashes.md` + +```markdown +# N. Title + +## Status + +Proposed | Accepted | Deprecated | Superseded by [ADR-NNNN] + +## Context + +What is the issue that we're seeing that is motivating this decision or change? + +## Decision + +What is the change that we're proposing and/or doing? + +## Consequences + +What becomes easier or more difficult to do because of this change? +``` + +## Rules + +- ADRs are immutable once accepted — supersede, don't edit +- Keep context focused on forces at play at the time of the decision +- Write consequences as both positive and negative impacts +- Number sequentially, never reuse numbers +- Title should be a short noun phrase (e.g. "Use PostgreSQL for persistence") diff --git a/deployment/homelab/images/opencode/config/skills/conventional-commits/SKILL.md b/deployment/homelab/images/opencode/config/skills/conventional-commits/SKILL.md new file mode 100644 index 000000000000..7a9a63f24165 --- /dev/null +++ b/deployment/homelab/images/opencode/config/skills/conventional-commits/SKILL.md @@ -0,0 +1,36 @@ +--- +name: conventional-commits +description: Conventional Commits specification for structured commit messages +--- + +# Conventional Commits + +## Format + +``` +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +## Types + +- `feat`: A new feature (correlates with MINOR in SemVer) +- `fix`: A bug fix (correlates with PATCH in SemVer) +- `docs`: Documentation only changes +- `style`: Changes that do not affect the meaning of the code +- `refactor`: A code change that neither fixes a bug nor adds a feature +- `perf`: A code change that improves performance +- `test`: Adding missing tests or correcting existing tests +- `chore`: Changes to the build process or auxiliary tools + +## Rules + +- Subject line must not exceed 72 characters +- Use imperative mood in the subject line ("add" not "added") +- Do not end the subject line with a period +- Separate subject from body with a blank line +- Use the body to explain what and why, not how +- `BREAKING CHANGE:` footer or `!` after type/scope for breaking changes diff --git a/deployment/homelab/src/index.ts b/deployment/homelab/src/index.ts index 8ed6d62ff24a..d695584020fd 100644 --- a/deployment/homelab/src/index.ts +++ b/deployment/homelab/src/index.ts @@ -48,6 +48,9 @@ const opencodeImage = cfg.require("opencodeImage") const anthropicApiKey = cfg.requireSecret("anthropicApiKey") const defaultGitRepo = cfg.get("defaultGitRepo") const storageSize = cfg.get("storageSize") ?? "2Gi" +// podEnv: optional multiline .env content injected into session pods via ConfigMap. +// Operators set arbitrary env vars here (e.g. "WORKFLOW_AGENTS=ade\nOPENCODE_MODEL=..."). +const podEnv = cfg.get("podEnv") ?? "" const cfApiToken = cloudflareConfig.requireSecret("apiToken") // --------------------------------------------------------------------------- @@ -178,6 +181,10 @@ const configMap = new k8s.core.v1.ConfigMap( null, 2, ), + // .env is sourced by the pod entrypoint before launching opencode. + // Operators set arbitrary vars here, e.g. WORKFLOW_AGENTS=ade. + // Empty by default — the source command is guarded with "|| true". + ".env": podEnv, }, }, { dependsOn: [ns] }, diff --git a/packages/opencode-router/src/pod-manager.test.ts b/packages/opencode-router/src/pod-manager.test.ts index a707e02140af..6025b6176ed2 100644 --- a/packages/opencode-router/src/pod-manager.test.ts +++ b/packages/opencode-router/src/pod-manager.test.ts @@ -364,7 +364,7 @@ describe("suggestBranch", () => { const seq = ["calm-snails-dream", "bold-frogs-dance", "swift-hawks-fly"] let idx = 0 mock.module("human-id", () => ({ - default: (_opts: object) => seq[idx++ % seq.length], + humanId: (_opts: object) => seq[idx++ % seq.length], })) // Pre-populate PVCs for the first two candidates so they collide @@ -384,7 +384,7 @@ describe("suggestBranch", () => { // Mock human-id to always return the same name (infinite collision) const fixed = "slow-bears-roam" mock.module("human-id", () => ({ - default: (_opts: object) => fixed, + humanId: (_opts: object) => fixed, })) // Pre-populate a PVC for that name so every attempt collides @@ -435,7 +435,7 @@ describe("SessionKey.sourceBranch", () => { const pod = (createPodCalls[0] as any).body const script: string = pod.spec.initContainers[0].args[0] // Script must checkout the sourceBranch before creating the new sessionBranch - expect(script).toContain("main") // sourceBranch checkout + expect(script).toContain("main") // sourceBranch checkout expect(script).toContain("calm-snails-dream") // new session branch creation // Must NOT try to look up "calm-snails-dream" on remote (it's always a new branch) expect(script).not.toContain('ls-remote --exit-code --heads origin "calm-snails-dream"') diff --git a/packages/opencode-router/src/pod-manager.ts b/packages/opencode-router/src/pod-manager.ts index 72b8f74b5ae9..294b923429d3 100644 --- a/packages/opencode-router/src/pod-manager.ts +++ b/packages/opencode-router/src/pod-manager.ts @@ -1,7 +1,7 @@ import crypto from "node:crypto" import fs from "node:fs" import * as k8s from "@kubernetes/client-node" -import humanId from "human-id" +import { humanId } from "human-id" import { config } from "./config.js" const kc = new k8s.KubeConfig() @@ -160,18 +160,27 @@ export async function ensurePod(session: SessionKey): Promise { // accordingly so opencode serve starts in the git repo and correctly discovers the project. const workspacePath = `/home/opencode/projects` - // Use GIT_SAFE="git -c safe.directory=/workspace" to avoid needing a writable $HOME - // for "git config --global". The -c flag applies the config inline for each invocation. - const gitInitScript = [ + // Single init container using the opencode image (which already has git). + // Phase 1 — config seed (idempotent): copy /etc/opencode-defaults → ~/.config/opencode on first start, + // then run any *.sh scripts in init-scripts/ (e.g. skills install). Skipped on pod restart. + // Phase 2 — git: clone repo + checkout session branch. Safe.directory avoids needing a writable HOME. + const initScript = [ `set -e`, + // --- config phase (idempotent) --- + `if [ ! -d /home/opencode/.config/opencode ]; then`, + ` mkdir -p /home/opencode/.config/opencode`, + ` cp -r /etc/opencode-defaults/. /home/opencode/.config/opencode/`, + ` for s in /etc/opencode-defaults/init-scripts/*.sh; do`, + ` [ -f "$s" ] && sh "$s" || true`, + ` done`, + `fi`, + // --- git phase --- `GIT="git -c safe.directory=/workspace"`, `if [ ! -d /workspace/.git ]; then`, ` git clone "${repoUrl}" /workspace`, `fi`, `cd /workspace`, `$GIT fetch --all`, - // Switch to session branch: if it already exists locally (resume), just check it out. - // Otherwise establish the source branch as starting point and create the session branch. `if $GIT rev-parse --verify "${branch}" >/dev/null 2>&1; then`, ` $GIT checkout "${branch}"`, `else`, @@ -180,25 +189,26 @@ export async function ensurePod(session: SessionKey): Promise { `fi`, ].join("\n") + const secCtx: k8s.V1SecurityContext = { + runAsUser: 1000, + runAsGroup: 1000, + allowPrivilegeEscalation: false, + runAsNonRoot: true, + capabilities: { drop: ["ALL"] }, + seccompProfile: { type: "RuntimeDefault" }, + } + const initContainers: k8s.V1Container[] = [ { - name: "git-init", - securityContext: { - runAsUser: 1000, - runAsGroup: 1000, - allowPrivilegeEscalation: false, - runAsNonRoot: true, - capabilities: { drop: ["ALL"] }, - seccompProfile: { type: "RuntimeDefault" }, - }, - image: "alpine/git:latest", + name: "init", + securityContext: secCtx, + image: config.opencodeImage, command: ["sh", "-c"], - args: [gitInitScript], - env: [ - // Provide a writable HOME so git doesn't try to write to /.gitconfig - { name: "HOME", value: "/tmp" }, + args: [initScript], + volumeMounts: [ + { name: "user-data", mountPath: "/home/opencode" }, + { name: "user-data", mountPath: "/workspace", subPath: "projects" }, ], - volumeMounts: [{ name: "user-data", mountPath: "/workspace", subPath: "projects" }], }, ] @@ -235,7 +245,13 @@ export async function ensurePod(session: SessionKey): Promise { // The main container mounts the full PVC at /home/opencode, so the repo is at // /home/opencode/projects/. workingDir: workspacePath, - command: ["opencode", "serve", "--hostname", "0.0.0.0", "--port", String(config.opencodePort)], + // Source /home/opencode/.opencode/.env if present — lets operators inject arbitrary + // env vars (e.g. WORKFLOW_AGENTS) via the existing ConfigMap without touching router code. + command: [ + "sh", + "-c", + `set -a; . /home/opencode/.opencode/.env 2>/dev/null || true; set +a; exec opencode serve --hostname 0.0.0.0 --port ${config.opencodePort}`, + ], ports: [{ containerPort: config.opencodePort }], envFrom: [{ secretRef: { name: config.apiKeySecretName } }], securityContext: { @@ -493,7 +509,7 @@ function isConflict(err: unknown): boolean { export async function suggestBranch(email: string, repoUrl: string): Promise { let candidate = "" for (let i = 0; i < 10; i++) { - candidate = (humanId as unknown as (opts?: object) => string)({ separator: "-", capitalize: false }) + candidate = humanId({ separator: "-", capitalize: false }) const hash = getSessionHash(email, repoUrl, candidate) try { await k8sApi.readNamespacedPersistentVolumeClaim({ name: pvcName(hash), namespace: config.namespace }) diff --git a/packages/opencode-router/tsconfig.json b/packages/opencode-router/tsconfig.json index a1067da475b3..454b88a43d26 100644 --- a/packages/opencode-router/tsconfig.json +++ b/packages/opencode-router/tsconfig.json @@ -10,5 +10,6 @@ "esModuleInterop": true, "skipLibCheck": true }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/**/*.test.ts"] } From ca32f1964be6fa811d56f183a8f319837a122202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Mon, 13 Apr 2026 15:03:56 +0200 Subject: [PATCH 011/616] ci: migrate homelab deploy to reusable Tailscale workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the inline pulumi-up steps with a call to the reusable mrsimpson/homelab/.github/workflows/deploy-to-cluster.yml workflow. This is the reference implementation of the Tailscale-based CI/CD pattern: the reusable workflow joins the tailnet ephemerally, writes the kubeconfig, and runs pulumi up — so the opencode repo only needs to pass stack name, working directory, and cluster secrets. Image tag overrides (routerImage, cfOperatorImage via workflow_dispatch) are handled in a pre-job that runs pulumi config set before the deploy job, since stack config updates are app-specific and cannot live in the reusable workflow. Also removes the unused id-token: write permission. --- .github/workflows/deploy-homelab.yml | 88 ++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 25 deletions(-) diff --git a/.github/workflows/deploy-homelab.yml b/.github/workflows/deploy-homelab.yml index 5fd032927162..90e078731adb 100644 --- a/.github/workflows/deploy-homelab.yml +++ b/.github/workflows/deploy-homelab.yml @@ -1,5 +1,21 @@ name: Deploy to homelab +# Deploys the opencode stack to the homelab k3s cluster. +# +# Uses the reusable workflow from mrsimpson/homelab which handles: +# - Tailscale VPN join (to reach the local cluster API) +# - kubeconfig setup +# - pulumi up / preview +# +# Pulumi project: "opencode" (Pulumi.yaml name: opencode) +# Stack: mrsimpson/opencode/dev +# Base infra read via: StackReference("mrsimpson/homelab/dev") +# +# Required secrets (org-level): +# TS_OAUTH_CLIENT_ID, TS_OAUTH_CLIENT_SECRET — Tailscale OAuth client +# KUBECONFIG — base64-encoded kubeconfig (Tailscale address) +# PULUMI_ACCESS_TOKEN — Pulumi Cloud access token + on: # Run after images are built workflow_run: @@ -15,51 +31,73 @@ on: workflow_dispatch: inputs: routerImage: - description: "opencode-router image (leave blank to use current stack config)" + description: "opencode-router image tag (leave blank to keep current stack config)" type: string required: false cfOperatorImage: - description: "cloudflare-operator image (leave blank to use current stack config)" + description: "cloudflare-operator image tag (leave blank to keep current stack config)" type: string required: false permissions: contents: read - id-token: write # for OIDC-based kubeconfig (if used) jobs: - deploy: + # Optional pre-step: update image tags in Pulumi stack config before deploying. + # Only runs when image overrides are provided via workflow_dispatch. + # Skipped entirely on workflow_run triggers (images are already baked into config). + update-image-config: + name: Update image config runs-on: ubuntu-latest - # Only deploy if the triggering workflow succeeded (or if manually triggered) - if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} + if: > + github.event_name == 'workflow_dispatch' && + (inputs.routerImage != '' || inputs.cfOperatorImage != '') + steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 with: node-version: "24" + cache: npm + cache-dependency-path: deployment/homelab/package-lock.json - - name: Install Pulumi CLI - uses: pulumi/actions@v6 - - - name: Install homelab stack dependencies - run: npm install + - name: Install dependencies + run: npm ci working-directory: deployment/homelab - - name: Update image tags (if provided) - if: ${{ inputs.routerImage != '' || inputs.cfOperatorImage != '' }} + - name: Update image tags in Pulumi config run: | - cd deployment/homelab - [ -n "${{ inputs.routerImage }}" ] && pulumi config set code:routerImage "${{ inputs.routerImage }}" - [ -n "${{ inputs.cfOperatorImage }}" ] && pulumi config set code:cfOperatorImage "${{ inputs.cfOperatorImage }}" + [ -n "${{ inputs.routerImage }}" ] && \ + pulumi config set code:routerImage "${{ inputs.routerImage }}" \ + --cwd deployment/homelab + [ -n "${{ inputs.cfOperatorImage }}" ] && \ + pulumi config set code:cfOperatorImage "${{ inputs.cfOperatorImage }}" \ + --cwd deployment/homelab env: PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} - - name: Pulumi up - run: pulumi up --yes --non-interactive - working-directory: deployment/homelab - env: - PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }} - # HOMELAB_KUBECONFIG: base64-encoded kubeconfig for the homelab cluster. - # Set as a repo secret. The Pulumi k8s provider reads KUBECONFIG from env. - KUBECONFIG: ${{ secrets.HOMELAB_KUBECONFIG }} + # Main deploy job — calls the reusable workflow from the homelab repo. + # This is the 1:1 reference implementation of the Tailscale-based CI/CD pattern. + deploy: + name: Deploy + needs: [update-image-config] + # Run if: triggered by workflow_run (skip config update) OR config update succeeded + if: > + always() && ( + github.event_name != 'workflow_dispatch' || + needs.update-image-config.result == 'success' || + needs.update-image-config.result == 'skipped' + ) && ( + github.event_name != 'workflow_run' || + github.event.workflow_run.conclusion == 'success' + ) + uses: mrsimpson/homelab/.github/workflows/deploy-to-cluster.yml@main + with: + # Pulumi project "opencode" (from deployment/homelab/Pulumi.yaml "name: opencode") + # NOT mrsimpson/homelab/dev — that is the base infra stack read via StackReference + pulumi-stack: mrsimpson/opencode/dev + working-directory: deployment/homelab + pulumi-command: up + secrets: inherit From c6cdc1bcdfb3c7cc9cc2aa7a0ad1080e485745f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Mon, 13 Apr 2026 19:29:36 +0200 Subject: [PATCH 012/616] chore(fork): make fork independent from upstream - Add fork-validate.yml CI: typecheck + test opencode-router and opencode-router-app; compatibility-guard blocks upstream file modifications in PRs - Add .github/fork/disable-upstream-workflows.sh: idempotently disables 23 irrelevant upstream workflows server-side via gh CLI - Add FORK.md: contributor guide covering packages, branches, CI, upstream merge procedure - Prepend fork banner to README.md linking to FORK.md - Revert AI-agent noise from packages/opencode/package.json (random/clean/lint/format/docs/deploy scripts + randomField) --- .github/fork/disable-upstream-workflows.sh | 89 ++++++ .github/workflows/fork-validate.yml | 164 +++++++++++ .../development-plan-make-fork-independent.md | 260 ++++++++++++++++++ FORK.md | 98 +++++++ README.md | 4 + packages/opencode/package.json | 7 - 6 files changed, 615 insertions(+), 7 deletions(-) create mode 100755 .github/fork/disable-upstream-workflows.sh create mode 100644 .github/workflows/fork-validate.yml create mode 100644 .vibe/development-plan-make-fork-independent.md create mode 100644 FORK.md diff --git a/.github/fork/disable-upstream-workflows.sh b/.github/fork/disable-upstream-workflows.sh new file mode 100755 index 000000000000..64f1d431678a --- /dev/null +++ b/.github/fork/disable-upstream-workflows.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# .github/fork/disable-upstream-workflows.sh +# +# Disables upstream workflows that are irrelevant or harmful for this fork. +# Run this after every upstream merge to catch newly added noisy workflows. +# +# Usage: +# bash .github/fork/disable-upstream-workflows.sh +# bash .github/fork/disable-upstream-workflows.sh --dry-run +# +# Requirements: gh CLI authenticated to the fork repo (mrsimpson/opencode) + +set -euo pipefail + +REPO="mrsimpson/opencode" +DRY_RUN=false + +for arg in "$@"; do + [[ "$arg" == "--dry-run" ]] && DRY_RUN=true +done + +disable() { + local name="$1" + local reason="$2" + echo "Disabling: $name ($reason)" + if [[ "$DRY_RUN" == "false" ]]; then + gh workflow disable "$name" --repo "$REPO" 2>/dev/null || \ + echo " → skipped (may already be disabled or not found)" + fi +} + +echo "==> Fork: $REPO" +echo "==> Disabling upstream workflows irrelevant to this fork" +[[ "$DRY_RUN" == "true" ]] && echo "==> DRY RUN — no changes will be made" +echo "" + +# ── Scheduled maintenance bots ───────────────────────────────────────────── +disable "close-issues.yml" "daily bot irrelevant to fork's issue tracker" +disable "close-stale-prs.yml" "daily stale-PR bot irrelevant to fork" +disable "compliance-close.yml" "runs every 30 min, irrelevant to fork" +disable "daily-issues-recap.yml" "daily recap irrelevant to fork" +disable "daily-pr-recap.yml" "daily recap irrelevant to fork" + +# ── Issue & PR triage bots ───────────────────────────────────────────────── +disable "triage.yml" "AI triage bot not configured for fork" +disable "duplicate-issues.yml" "duplicate-detection bot adds noise to fork" +disable "review.yml" "upstream review bot not appropriate for fork" +disable "pr-management.yml" "upstream team-membership checks will fail in fork" +disable "opencode.yml" "/oc slash commands require bot tokens not in fork" + +# ── Upstream CI (tests/typechecks/lint for upstream packages) ────────────── +disable "test.yml" "tests upstream packages; fork has fork-validate.yml" +disable "typecheck.yml" "typechecks all packages; fork has fork-validate.yml" +disable "storybook.yml" "Storybook not used by fork" + +# ── Nix (not used in fork) ───────────────────────────────────────────────── +disable "nix-eval.yml" "Nix not used in fork" +disable "nix-hashes.yml" "Nix not used in fork" + +# ── Upstream build/deploy (builds upstream artifacts, not fork artifacts) ── +disable "containers.yml" "builds upstream containers; fork has build-opencode-router.yml" +disable "deploy.yml" "SST/AWS deploy for upstream; fork has deploy-homelab.yml" +disable "generate.yml" "SDK generation creates unwanted PRs in fork" +disable "beta.yml" "upstream beta branch sync irrelevant to fork" + +# ── Vouch system (upstream governance) ───────────────────────────────────── +disable "vouch-check-pr.yml" "upstream author-vouching system not used in fork" +disable "vouch-check-issue.yml" "upstream author-vouching system not used in fork" +disable "vouch-manage-by-issue.yml" "upstream author-vouching system not used in fork" + +# ── Release pipelines (upstream publishing) ──────────────────────────────── +disable "release-github-action.yml" "upstream GitHub Action release; not applicable to fork" + +echo "" +echo "==> Done." +echo "" +echo "Workflows intentionally kept enabled:" +echo " fork-validate.yml — fork PR validation (typecheck, tests, compatibility)" +echo " build-opencode-router.yml — fork Docker image build" +echo " build-cloudflare-operator.yml — fork Docker image build" +echo " deploy-homelab.yml — fork homelab deployment" +echo " pr-standards.yml — conventional commit enforcement (kept)" +echo " publish.yml — already guarded: if: repo == 'anomalyco/opencode'" +echo " stats.yml — already guarded: if: repo == 'anomalyco/opencode'" +echo " docs-update.yml — already guarded: if: repo == 'sst/opencode'" +echo " notify-discord.yml — release-only trigger, harmless" +echo " sync-zed-extension.yml — release-only trigger, harmless" +echo " publish-github-action.yml — tag-only trigger, harmless" +echo " publish-vscode.yml — tag-only trigger, harmless" diff --git a/.github/workflows/fork-validate.yml b/.github/workflows/fork-validate.yml new file mode 100644 index 000000000000..ab55d3b3e9d5 --- /dev/null +++ b/.github/workflows/fork-validate.yml @@ -0,0 +1,164 @@ +name: Fork validation + +# Validates fork-specific packages on every PR and push to main. +# This is the authoritative CI check for fork contributors. +# +# Jobs: +# validate-router — typecheck + test opencode-router (pnpm/Node.js) +# validate-router-app — typecheck + test opencode-router-app (bun) +# compatibility-guard — warn if upstream package files were modified + +on: + pull_request: + branches: + - main + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + +jobs: + validate-router: + name: Validate opencode-router + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/opencode-router + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: latest + run_install: false + + - name: Get pnpm store path + id: pnpm-cache + run: echo "dir=$(pnpm store path)" >> $GITHUB_OUTPUT + + - name: Cache pnpm store + uses: actions/cache@v4 + with: + path: ${{ steps.pnpm-cache.outputs.dir }} + key: ${{ runner.os }}-pnpm-router-${{ hashFiles('packages/opencode-router/pnpm-lock.yaml') }} + restore-keys: ${{ runner.os }}-pnpm-router- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Typecheck + run: pnpm typecheck + + - name: Test + run: pnpm test + + validate-router-app: + name: Validate opencode-router-app + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + # Install from root so workspace deps (e.g. @opencode-ai/ui) resolve correctly + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Typecheck + run: bun typecheck + working-directory: packages/opencode-router-app + + - name: Test + run: bun test src/ + working-directory: packages/opencode-router-app + + - name: Build + run: bun run build + working-directory: packages/opencode-router-app + + compatibility-guard: + name: Compatibility guard + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Fetch upstream + run: | + git remote add upstream https://github.com/sst/opencode.git + git fetch upstream dev --depth=50 + + - name: Check for upstream file modifications + id: check + run: | + # Upstream-owned paths — fork should never modify these + UPSTREAM_PATHS=( + "packages/opencode/" + "packages/app/" + "packages/ui/" + "packages/sdk/" + "packages/desktop/" + ) + + # Files changed in this PR vs the base branch + CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) + + VIOLATIONS="" + for path in "${UPSTREAM_PATHS[@]}"; do + matches=$(echo "$CHANGED" | grep "^${path}" || true) + if [ -n "$matches" ]; then + VIOLATIONS="${VIOLATIONS}${matches}\n" + fi + done + + if [ -n "$VIOLATIONS" ]; then + echo "violations=true" >> $GITHUB_OUTPUT + printf "files<> $GITHUB_OUTPUT + else + echo "violations=false" >> $GITHUB_OUTPUT + fi + + - name: Comment on PR if upstream files modified + if: steps.check.outputs.violations == 'true' + uses: actions/github-script@v7 + with: + script: | + const files = `${{ steps.check.outputs.files }}`.trim() + const body = [ + '## ❌ Compatibility violation', + '', + 'This PR modifies upstream-owned files. These changes will cause **merge conflicts** when syncing with upstream.', + '', + 'Modified upstream files:', + '```', + files, + '```', + '', + 'Please revert changes to upstream-owned paths and keep fork changes in fork-owned paths only.', + 'Fork-owned paths: `packages/opencode-router/`, `packages/opencode-router-app/`, `deployment/`, `.github/workflows/build-*.yml`, `.github/workflows/deploy-homelab.yml`, `.github/fork/`', + ].join('\n') + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }) + + core.setFailed(`Upstream-owned files modified:\n${files}`) diff --git a/.vibe/development-plan-make-fork-independent.md b/.vibe/development-plan-make-fork-independent.md new file mode 100644 index 000000000000..d30ac9d5c98b --- /dev/null +++ b/.vibe/development-plan-make-fork-independent.md @@ -0,0 +1,260 @@ +# Development Plan: opencode (make-fork-independent branch) + +_Generated on 2026-04-13 by Vibe Feature MCP_ +_Workflow: [epcc](https://mrsimpson.github.io/responsible-vibe-mcp/workflows/epcc)_ + +## Goal + +Optimize this fork of `sst/opencode` (now `anomalyco/opencode`) so that: + +1. Fork-specific extensions remain **non-destructive** and **additive only** +2. Upstream merges stay easy (minimal diff surface, no upstream file pollution) +3. Developer experience (DX) is smooth for contributors working on the fork + +## Key Decisions + +### Separation Strategy + +- Fork-specific packages (`opencode-router`, `opencode-router-app`) remain in their own directories — **no changes to upstream packages** +- `deployment/` stays outside Bun workspace deliberately (uses npm/Node.js to avoid Bun catalog: incompatibility with Pulumi) +- Fork-specific CI workflows are additive files in `.github/workflows/` — never modify upstream workflow files + +### What NOT to do + +- Do not touch `packages/opencode/`, `packages/app/`, `packages/ui/`, `packages/sdk/js/`, `packages/desktop/`, or any other upstream packages +- Do not modify root `package.json` beyond what's strictly needed (e.g. adding fork packages to workspaces) +- Do not add fork-specific config to files shared with upstream (e.g. `turbo.json`, `tsconfig.json`, `bunfig.toml`) +- Do not modify `AGENTS.md` (upstream file; fork contributors can rely on fork-specific docs instead) + +### Noise to clean up + +- `packages/opencode/package.json` has AI-agent artifacts: `"randomField"`, placeholder scripts (`random`, `clean`, `lint`, `format`, `docs`, `deploy`) — these must be reverted/removed +- These are the **only changes to upstream files** needed + +### Disabling upstream workflows — DRY via `gh workflow disable` + +**Decision:** Use `gh workflow disable --repo mrsimpson/opencode` for each unwanted workflow. **No files are touched.** + +- State stored server-side by GitHub; zero merge conflict risk on upstream merges +- Self-documenting: a script `.github/fork/disable-upstream-workflows.sh` lists every disabled workflow with a comment explaining why +- After every upstream merge, re-run the script to catch any newly added noisy workflows +- To re-enable a workflow: `gh workflow enable ` + +**Workflows to keep enabled (fork-relevant or harmless):** + +- `build-opencode-router.yml`, `build-cloudflare-operator.yml`, `deploy-homelab.yml` — fork-owned, should run +- `pr-standards.yml` — useful conventional commit enforcement, keep +- `publish.yml`, `stats.yml`, `docs-update.yml` — already guarded by `if: repo == upstream`, harmless + +**Workflows to disable (all others):** see `.github/fork/disable-upstream-workflows.sh` + +### Documentation strategy + +- Do **not** update `AGENTS.md` (upstream file — skip entirely) +- Add a **"Fork extensions"** section at the very top of `README.md` (above the upstream logo/badges block) with a brief description and links to each fork package +- Create `FORK.md` as the canonical contributor guide for the fork: upstream merge procedure, fork packages, branch strategy, CI management +- Fork-specific scripts go in `.github/fork/` (new directory, never conflicts with upstream) + +### README strategy + +- `README.md` is upstream but we prepend a small fork-specific section at the top +- This section survives merges if kept minimal (upstream adds to the file's interior; a top-of-file prepend rarely conflicts) +- Section links to `FORK.md` and each fork package's own README + +## Notes + +### Repo context + +- **Upstream:** `https://github.com/sst/opencode` (redirects to `anomalyco/opencode`) +- **Fork:** `https://github.com/mrsimpson/opencode` +- **Default branch:** `dev` (tracks upstream `dev`) +- **Working branch:** `make-fork-independent` +- **Commits ahead of upstream/dev:** 11 commits, all additive except the noisy `packages/opencode/package.json` + +### Fork-specific components + +| Component | Location | Purpose | +| ------------------- | -------------------------------------------------- | ------------------------------------------- | +| opencode-router | `packages/opencode-router/` | K8s per-user pod isolation HTTP/WS proxy | +| opencode-router-app | `packages/opencode-router-app/` | SolidJS session management SPA | +| homelab deployment | `deployment/homelab/` | Pulumi stack for k3s cluster | +| cloudflare operator | `deployment/opencode-cloudflare-operator/` | K8s operator for per-session Cloudflare DNS | +| fork CI | `.github/workflows/build-opencode-router.yml` etc. | GHCR image builds + homelab deploy | + +### DX improvements identified + +- Add a `FORK.md` clearly documenting fork structure, upstream merge procedure, and fork-specific packages for contributors +- Ensure `upstream` git remote is documented +- Add fork-specific packages to a clearly labelled section in root `package.json` workspaces +- Consider a `fork-check` CI step that warns if upstream files were accidentally modified + +### Branch Strategy + +The user wants to rename the fork's default branch from `dev` to `main`. This cleanly separates fork identity from upstream tracking. + +**Current state:** + +- Fork default: `dev` (same name as upstream) — causes confusion +- `upstream/dev` = upstream integration branch +- Goal: `main` = fork integration branch, `dev` kept only as local tracking ref for upstream + +**What references `dev` and must be updated (fork-owned files only):** + +| File | Reference | Notes | +| ------------------------------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| `AGENTS.md` | `The default branch in this repo is \`dev\``and`use \`dev\` or \`origin/dev\` for diffs` | Must update to `main` | +| `.github/workflows/build-opencode-router.yml` | `branches: - dev` | Fork-owned → update to `main` | +| `.github/workflows/build-cloudflare-operator.yml` | `branches: - dev` | Fork-owned → update to `main` | +| `.github/workflows/deploy-homelab.yml` | `branches: - dev`, `pulumi-stack: mrsimpson/opencode/dev` | Fork-owned → update branch to `main`; Pulumi stack name is separate concern | + +**Upstream workflow files referencing `dev` (do NOT change — will conflict on merge):** + +| File | Reference | Strategy | +| --------------------------- | --------------------------------------------------------------- | ---------------------------------------------------- | +| `test.yml` | `branches: - dev`, concurrency group check for `refs/heads/dev` | Leave as-is (will be disabled via repo guard anyway) | +| `typecheck.yml` | `branches: [dev]` | Leave as-is | +| `nix-eval.yml` | `branches: [dev]` | Leave as-is | +| `nix-hashes.yml` | `branches: [dev, beta]` | Leave as-is | +| `storybook.yml` | `branches: [dev]` | Leave as-is | +| `generate.yml` | `branches: - dev` (push trigger) | Leave as-is | +| `containers.yml` | `branches: - dev` | Leave as-is | +| `deploy.yml` | `branches: - dev` | Leave as-is | +| `release-github-action.yml` | `branches: - dev` | Leave as-is | +| `publish.yml` | `branches: - dev` | Leave as-is (already guarded) | +| `docs-locale-sync.yml` | `branches: - dev` | Leave as-is (already disabled) | +| `pr-standards.yml` | `ref: 'dev'` | Leave as-is | + +**Pulumi stack name:** The Pulumi stack `mrsimpson/opencode/dev` is a Pulumi Cloud concept (org/project/stack), separate from the git branch name. Renaming the git branch does not require renaming the Pulumi stack — but `deploy-homelab.yml` references it; leave Pulumi stack name as `dev` for now (it's a cloud resource, renaming is a separate operation). + +**Git operations needed:** + +1. Rename local `dev` → `main` +2. Push `main` to origin +3. Set `origin/HEAD` → `main` +4. Update GitHub default branch setting (via `gh` CLI or UI) +5. Keep `dev` as a local-only tracking branch for `upstream/dev` + +### CI Strategy (Revised: Explicit over Negative) + +**Key decision:** Rather than primarily disabling upstream workflows, we define **explicit fork CI** that covers everything the fork needs. This is additive, positive, and clear to contributors. + +**Fork CI coverage:** + +| Need | Workflow | Trigger | +| ------------------------------------------- | ------------------------------- | -------------------------------- | +| Validate PR changes to fork packages | `fork-validate.yml` | `pull_request`, `push` to `main` | +| Build + publish `opencode-router` image | `build-opencode-router.yml` | push to `main` (paths filter) | +| Build + publish `cloudflare-operator` image | `build-cloudflare-operator.yml` | push to `main` (paths filter) | +| Deploy to homelab | `deploy-homelab.yml` | `workflow_run` after image build | + +**`fork-validate.yml` jobs:** + +1. `validate-router` — install pnpm deps, typecheck (`tsc --noEmit`), run tests (`bun test`) +2. `validate-router-app` — install bun deps from **repo root** (so workspace deps like `@opencode-ai/ui` resolve), typecheck + test + build in package dir +3. `compatibility-guard` — detect if any upstream package files were modified vs PR base branch; post a warning comment on PR if so + +**Design notes:** + +- `opencode-router` uses pnpm (has its own `pnpm-lock.yaml`); installed in package dir +- `opencode-router-app` uses bun and has workspace deps → `bun install` must run at repo root +- Compatibility guard is a **blocking failure** (`core.setFailed`): upstream file modifications must not land in `main`; posts a PR comment with the violated files + +**Disabling upstream workflows (secondary):** still useful to reduce noise and wasted CI minutes on the fork. The disable script in `.github/fork/disable-upstream-workflows.sh` documents what was disabled and why. + +### CI/CD Analysis + +**35 upstream workflows** are inherited — the vast majority are irrelevant or even harmful for this fork: + +| Workflow | Status | Reason | +| --------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | +| `publish.yml` | Already guarded — `if: github.repository == 'anomalyco/opencode'` on all jobs | Will never run on fork | +| `stats.yml` | Already guarded — `if: github.repository == 'anomalyco/opencode'` | Will never run on fork | +| `docs-update.yml` | Already guarded — `if: github.repository == 'sst/opencode'` | Will never run on fork | +| `test.yml` | **RUNS on fork** — tests `packages/opencode` and `packages/app` (upstream code) on every PR/push | Unnecessary for fork; wastes CI minutes; uses expensive Blacksmith runners + Windows matrix | +| `typecheck.yml` | **RUNS on fork** — typechecks all packages including upstream ones | Partly useful but runs on upstream code we don't change | +| `nix-eval.yml` | **RUNS on fork** | No Nix changes expected in fork | +| `nix-hashes.yml` | **RUNS on fork** | No Nix changes expected in fork | +| `beta.yml` | **RUNS on fork** (schedule + dispatch) — tries to sync beta branch | Meaningless for fork | +| `generate.yml` | **RUNS on fork** (push to dev) — regenerates SDK, creates PRs | Will create unwanted automated PRs | +| `containers.yml` | **RUNS on fork** (push to dev) — builds upstream container images | Wastes CI time | +| `storybook.yml` | **RUNS on fork** | Storybook not used by fork | +| `triage.yml` | **RUNS on fork** — AI issue triage | Adds noise to fork's issues | +| `duplicate-issues.yml` | **RUNS on fork** | Adds noise | +| `review.yml` | **RUNS on fork** | Upstream review bot, not appropriate | +| `opencode.yml` | **RUNS on fork** — `/oc` slash commands using opencode AI bot | Bot tokens not available in fork | +| `pr-management.yml` | **RUNS on fork** | Upstream team-membership checks will fail | +| `pr-standards.yml` | **RUNS on fork** — conventional commit check | Could keep this | +| `close-issues.yml` | **RUNS on fork** (daily schedule) | Irrelevant | +| `close-stale-prs.yml` | **RUNS on fork** (daily schedule) | Irrelevant | +| `compliance-close.yml` | **RUNS on fork** (every 30 min schedule!) | Very noisy, irrelevant | +| `daily-issues-recap.yml` | **RUNS on fork** (daily schedule) | Irrelevant | +| `daily-pr-recap.yml` | **RUNS on fork** (daily schedule) | Irrelevant | +| `docs-locale-sync.yml` | Disabled — `if: false` in job | Not running | +| `notify-discord.yml` | Trigger on release only | Not applicable to fork | +| `deploy.yml` | **RUNS on fork** (push to dev) — SST/AWS deploy | Dangerous: will try to deploy upstream cloud infra | +| `vouch-check-pr.yml` | **RUNS on fork** — denounced author checks | Useless for fork | +| `vouch-check-issue.yml` | **RUNS on fork** | Useless | +| `vouch-manage-by-issue.yml` | **RUNS on fork** | Useless | +| `release-github-action.yml` | **RUNS on fork** (push to dev, paths: `github/**`) | Irrelevant | +| `sync-zed-extension.yml` | Trigger on release only | Not applicable | +| `publish-github-action.yml` | Trigger on tags only | Not applicable | +| `publish-vscode.yml` | Trigger on tags only | Not applicable | + +**Fork-specific workflows (already exist):** + +- `build-opencode-router.yml` ✅ — good +- `build-cloudflare-operator.yml` ✅ — good +- `deploy-homelab.yml` ✅ — good + +**Problem:** `opencode-router` has its own `pnpm-lock.yaml` and uses Node.js (not Bun), so it's outside the Bun workspace. Its tests run via `bun test` but the package is NOT included in `turbo.json` tasks. The `opencode-router-app` IS in the Bun workspace (`packages/*` glob) and has `test` and `typecheck` scripts, but turbo doesn't know about it. + +**Disabling strategy — DRY, zero file changes:** + +Use `gh workflow disable --repo mrsimpson/opencode` for each unwanted upstream workflow. This is: + +- **DRY**: state stored server-side by GitHub, no file changes needed +- **Zero merge conflict risk**: upstream files are never touched +- **Reversible**: `gh workflow enable` re-activates any workflow +- **Documented**: a shell script in the fork docs captures which workflows are disabled and why + +When upstream merges bring in new workflows, they start as "active" — the same script is re-run to catch any newly added noisy workflows. The script lives in `.github/fork/` (fork-owned, never conflicts with upstream). + +## Explore + + + +### Tasks + + + +_Auto-synced — do not edit here, use `bd` CLI instead._ + +## Plan + + + +### Tasks + + + +_Auto-synced — do not edit here, use `bd` CLI instead._ + +## Code + + + +### Tasks + + + +_Auto-synced — do not edit here, use `bd` CLI instead._ + +## Commit + + + +### Tasks + + + +_Auto-synced — do not edit here, use `bd` CLI instead._ diff --git a/FORK.md b/FORK.md new file mode 100644 index 000000000000..7b26d685e44c --- /dev/null +++ b/FORK.md @@ -0,0 +1,98 @@ +# Fork Guide: mrsimpson/opencode + +This is a fork of [anomalyco/opencode](https://github.com/anomalyco/opencode) extended with infrastructure components for self-hosted, multi-user homelab deployments. + +## Fork-specific packages + +| Package | Location | Purpose | +| --------------------------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------- | +| [opencode-router](packages/opencode-router/README.md) | `packages/opencode-router/` | HTTP/WebSocket reverse proxy providing per-user pod isolation on Kubernetes | +| [opencode-router-app](packages/opencode-router-app/) | `packages/opencode-router-app/` | SolidJS SPA for managing sessions (start, resume, terminate) | +| [homelab deployment](deployment/homelab/) | `deployment/homelab/` | Pulumi stack deploying the full stack to a k3s cluster | +| [cloudflare operator](deployment/opencode-cloudflare-operator/) | `deployment/opencode-cloudflare-operator/` | Kubernetes operator for per-session Cloudflare DNS | + +## Branch strategy + +| Branch | Purpose | +| ------ | --------------------------------------------------------------------- | +| `main` | Fork integration branch — all fork PRs target this | +| `dev` | Local tracking ref for `upstream/dev` — do not push fork changes here | + +```bash +# Set up git remotes after cloning +git remote add upstream https://github.com/sst/opencode.git +git fetch upstream +``` + +## CI/CD + +Fork CI is handled by explicit fork-owned workflows: + +| Workflow | Trigger | What it does | +| ------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------- | +| `fork-validate.yml` | PR → `main`, push to `main` | Typecheck + test `opencode-router` and `opencode-router-app`; blocks on upstream file modifications | +| `build-opencode-router.yml` | Push to `main` (paths filter) | Builds and pushes `opencode-router` Docker image to GHCR | +| `build-cloudflare-operator.yml` | Push to `main` (paths filter) | Builds and pushes `opencode-cloudflare-operator` Docker image to GHCR | +| `deploy-homelab.yml` | After successful image build | Deploys to homelab k3s cluster via Pulumi | + +Upstream workflows that are irrelevant to the fork are disabled server-side via: + +```bash +bash .github/fork/disable-upstream-workflows.sh +``` + +Re-run this script after every upstream merge to catch newly added upstream workflows. + +## Merging upstream + +```bash +# Fetch latest upstream +git fetch upstream + +# Merge into your local dev tracking branch +git checkout dev +git merge upstream/dev + +# Rebase or merge fork main onto updated dev +git checkout main +git merge dev # or: git rebase dev +``` + +If the merge produces conflicts in upstream-owned files (`packages/opencode/`, `packages/app/`, etc.), resolve by keeping the upstream version. Fork changes should only exist in fork-owned paths. + +**Fork-owned paths (never conflict with upstream):** + +- `packages/opencode-router/` +- `packages/opencode-router-app/` +- `deployment/` +- `.github/workflows/build-*.yml` +- `.github/workflows/deploy-homelab.yml` +- `.github/workflows/fork-validate.yml` +- `.github/fork/` +- `FORK.md` + +## Compatibility guard + +The `fork-validate.yml` CI workflow includes a `compatibility-guard` job that **fails** if any upstream-owned files are modified in a PR. This prevents accidental upstream file pollution. If a modification is intentional (e.g. reverting AI-injected noise), document the reason clearly in the PR description — but be aware the guard will still fail and require a bypass. + +## Local development + +```bash +# Install all dependencies (Bun workspace) +bun install + +# opencode-router (uses pnpm — separate lockfile) +cd packages/opencode-router +pnpm install +pnpm dev + +# opencode-router-app +cd packages/opencode-router-app +bun dev + +# Homelab deployment (uses npm) +cd deployment/homelab +npm install +``` + +See each package's own README for detailed setup instructions. diff --git a/README.md b/README.md index 79ccf8b34910..f3ea0974b084 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +> **This is a fork** — extended with self-hosted Kubernetes infrastructure. See [FORK.md](FORK.md) for fork-specific packages ([opencode-router](packages/opencode-router/README.md), [opencode-router-app](packages/opencode-router-app/), [homelab deployment](deployment/homelab/)), CI setup, and upstream merge instructions. + +--- +

diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 173bec6e8b83..eab2d23f214a 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -12,18 +12,11 @@ "build": "bun run script/build.ts", "upgrade-opentui": "bun run script/upgrade-opentui.ts", "dev": "bun run --conditions=browser ./src/index.ts", - "random": "echo 'Random script updated at $(date)' && echo 'Change queued successfully' && echo 'Another change made' && echo 'Yet another change' && echo 'One more change' && echo 'Final change' && echo 'Another final change' && echo 'Yet another final change'", - "clean": "echo 'Cleaning up...' && rm -rf node_modules dist", - "lint": "echo 'Running lint checks...' && bun test --coverage", - "format": "echo 'Formatting code...' && bun run --prettier --write src/**/*.ts", - "docs": "echo 'Generating documentation...' && find src -name '*.ts' -exec echo 'Processing: {}' \\;", - "deploy": "echo 'Deploying application...' && bun run build && echo 'Deployment completed successfully'", "db": "bun drizzle-kit" }, "bin": { "opencode": "./bin/opencode" }, - "randomField": "this-is-a-random-value-12345", "exports": { "./*": "./src/*.ts" }, From 0a3a206a41eac274803317ff671ab1de8ad8cb6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 07:04:12 +0200 Subject: [PATCH 013/616] =?UTF-8?q?fix(opencode-router):=20CI=20compatibil?= =?UTF-8?q?ity=20=E2=80=94=20lockfile,=20npmrc,=20Bun=20setup,=20test=20is?= =?UTF-8?q?olation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/fork-validate.yml | 3 +++ packages/opencode-router/.npmrc | 1 + packages/opencode-router/package.json | 2 +- packages/opencode-router/pnpm-lock.yaml | 9 +++++++++ packages/opencode-router/src/config.test.ts | 3 ++- 5 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 packages/opencode-router/.npmrc diff --git a/.github/workflows/fork-validate.yml b/.github/workflows/fork-validate.yml index ab55d3b3e9d5..e604b840ef57 100644 --- a/.github/workflows/fork-validate.yml +++ b/.github/workflows/fork-validate.yml @@ -37,6 +37,9 @@ jobs: with: node-version: "22" + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + - name: Setup pnpm uses: pnpm/action-setup@v4 with: diff --git a/packages/opencode-router/.npmrc b/packages/opencode-router/.npmrc new file mode 100644 index 000000000000..bf2e7648b076 --- /dev/null +++ b/packages/opencode-router/.npmrc @@ -0,0 +1 @@ +shamefully-hoist=true diff --git a/packages/opencode-router/package.json b/packages/opencode-router/package.json index 6ebe1b49869d..3c9339ebc221 100644 --- a/packages/opencode-router/package.json +++ b/packages/opencode-router/package.json @@ -8,7 +8,7 @@ "typecheck": "tsc --noEmit", "start": "node dist/index.js", "dev": "bun --env-file=.env.local --watch src/index.ts", - "test": "bun test" + "test": "bun test src/config.test.ts src/api.test.ts && bun test src/pod-manager.test.ts" }, "dependencies": { "@kubernetes/client-node": "^1.0.0", diff --git a/packages/opencode-router/pnpm-lock.yaml b/packages/opencode-router/pnpm-lock.yaml index 3a2b6afe6266..c98df87c3323 100644 --- a/packages/opencode-router/pnpm-lock.yaml +++ b/packages/opencode-router/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: http-proxy: specifier: ^1.18.1 version: 1.18.1 + human-id: + specifier: 4.1.3 + version: 4.1.3 devDependencies: '@tsconfig/node22': specifier: ^22.0.0 @@ -226,6 +229,10 @@ packages: resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} engines: {node: '>=8.0.0'} + human-id@4.1.3: + resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} + hasBin: true + ip-address@10.1.0: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} @@ -563,6 +570,8 @@ snapshots: transitivePeerDependencies: - debug + human-id@4.1.3: {} + ip-address@10.1.0: {} isomorphic-ws@5.0.0(ws@8.20.0): diff --git a/packages/opencode-router/src/config.test.ts b/packages/opencode-router/src/config.test.ts index 75b85cf58bab..4b2746e66fac 100644 --- a/packages/opencode-router/src/config.test.ts +++ b/packages/opencode-router/src/config.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from "bun:test" import { spawnSync } from "node:child_process" +import { resolve } from "node:path" describe("config defaults", () => { it("idleTimeoutMinutes defaults to 15", () => { @@ -8,7 +9,7 @@ describe("config defaults", () => { const result = spawnSync( process.execPath, ["--eval", "import('./src/config.ts').then(m => process.stdout.write(String(m.config.idleTimeoutMinutes)))"], - { env, encoding: "utf-8", cwd: "/Users/oliverjaegle/projects/open-source/opencode/packages/opencode-router" }, + { env, encoding: "utf-8", cwd: resolve(import.meta.dir, "..") }, ) expect(result.stdout.trim()).toBe("15") }) From 59ec3c0e3cc06360e23e5f7886e599721b8ffe91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 07:04:19 +0200 Subject: [PATCH 014/616] test(opencode-router): replace mock.module with DI, reset all mocks in beforeEach --- .../opencode-router/src/pod-manager.test.ts | 43 +++++-------------- packages/opencode-router/src/pod-manager.ts | 15 ++++++- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/packages/opencode-router/src/pod-manager.test.ts b/packages/opencode-router/src/pod-manager.test.ts index 6025b6176ed2..8bcf3f325ac2 100644 --- a/packages/opencode-router/src/pod-manager.test.ts +++ b/packages/opencode-router/src/pod-manager.test.ts @@ -1,4 +1,4 @@ -import { mock, describe, it, expect, beforeEach } from "bun:test" +import { describe, it, expect, beforeEach } from "bun:test" // Set required env vars before config module is loaded process.env.OPENCODE_IMAGE = "test" @@ -55,30 +55,11 @@ const fakeK8sApi = { }, } -// Mock @kubernetes/client-node before any import of pod-manager -mock.module("@kubernetes/client-node", () => { - return { - KubeConfig: class { - loadFromCluster() {} - loadFromDefault() {} - makeApiClient(_cls: unknown) { - return fakeK8sApi - } - }, - CoreV1Api: class {}, - } -}) - -// Also mock fs so the SA token path check doesn't matter -mock.module("node:fs", () => { - return { - default: { existsSync: (_path: string) => false }, - existsSync: (_path: string) => false, - } -}) - -// Dynamic import AFTER mocks are registered (bun evaluates mock.module synchronously) -const { listUserSessions, terminateSession, resumeSession, suggestBranch } = await import("./pod-manager.js") +// pod-manager.test.ts must run in its own bun process (see package.json test script) +// to avoid api.test.ts's mock.module("./pod-manager.js") poisoning this module import. +const { listUserSessions, terminateSession, resumeSession, suggestBranch, _setApiClient, _setHumanId } = + await import("./pod-manager.ts") +_setApiClient(fakeK8sApi as any) // --- Helpers --- @@ -360,12 +341,10 @@ describe("suggestBranch", () => { }) it("skips names that already exist as PVC hashes and returns the next unique one", async () => { - // Mock human-id to return a fixed sequence: first two collide, third is free + // Inject a deterministic humanId that returns a fixed sequence: first two collide, third is free const seq = ["calm-snails-dream", "bold-frogs-dance", "swift-hawks-fly"] let idx = 0 - mock.module("human-id", () => ({ - humanId: (_opts: object) => seq[idx++ % seq.length], - })) + _setHumanId((_opts?: any) => seq[idx++ % seq.length]) // Pre-populate PVCs for the first two candidates so they collide const hash0 = computeHash(EMAIL, REPO, seq[0]) @@ -381,11 +360,9 @@ describe("suggestBranch", () => { }) it("returns after at most 10 iterations even if all collide", async () => { - // Mock human-id to always return the same name (infinite collision) + // Inject a deterministic humanId that always returns the same name (infinite collision) const fixed = "slow-bears-roam" - mock.module("human-id", () => ({ - humanId: (_opts: object) => fixed, - })) + _setHumanId((_opts?: any) => fixed) // Pre-populate a PVC for that name so every attempt collides const hash = computeHash(EMAIL, REPO, fixed) diff --git a/packages/opencode-router/src/pod-manager.ts b/packages/opencode-router/src/pod-manager.ts index 294b923429d3..f08284c8e1da 100644 --- a/packages/opencode-router/src/pod-manager.ts +++ b/packages/opencode-router/src/pod-manager.ts @@ -1,7 +1,7 @@ import crypto from "node:crypto" import fs from "node:fs" import * as k8s from "@kubernetes/client-node" -import { humanId } from "human-id" +import { humanId as _humanId } from "human-id" import { config } from "./config.js" const kc = new k8s.KubeConfig() @@ -12,7 +12,18 @@ if (fs.existsSync("/var/run/secrets/kubernetes.io/serviceaccount/token")) { } else { kc.loadFromDefault() } -const k8sApi = kc.makeApiClient(k8s.CoreV1Api) +let k8sApi: k8s.CoreV1Api = kc.makeApiClient(k8s.CoreV1Api) +let humanId: typeof _humanId = _humanId + +/** For testing only: replace the k8s API client with a fake. */ +export function _setApiClient(client: k8s.CoreV1Api) { + k8sApi = client +} + +/** For testing only: replace the humanId generator. */ +export function _setHumanId(fn: typeof _humanId) { + humanId = fn +} const LABEL_SESSION_HASH = "opencode.ai/session-hash" const LABEL_MANAGED_BY = "app.kubernetes.io/managed-by" From 78719822f126286afbc6cf879dfada3997d828d6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Apr 2026 05:22:03 +0000 Subject: [PATCH 015/616] fix(ci): disable provenance attestation to fix GHCR 403 on push docker/build-push-action@v5+ enables provenance attestations by default. When pushing to a GHCR package that doesn't exist yet, GHCR returns 403 (instead of the expected 404) for blob existence checks triggered by the attestation flow. The Docker client interprets this as "unauthorized" and aborts the push. Setting provenance: false disables the OCI attestation push, allowing the GITHUB_TOKEN with packages: write to push the image as expected. Applied to both build-opencode-router and build-cloudflare-operator workflows. https://claude.ai/code/session_01RyMaDmCixspFQqgpPssYUp --- .github/workflows/build-cloudflare-operator.yml | 1 + .github/workflows/build-opencode-router.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build-cloudflare-operator.yml b/.github/workflows/build-cloudflare-operator.yml index 704031c72141..a1e56cfbf6f5 100644 --- a/.github/workflows/build-cloudflare-operator.yml +++ b/.github/workflows/build-cloudflare-operator.yml @@ -52,6 +52,7 @@ jobs: context: deployment/opencode-cloudflare-operator platforms: linux/amd64 push: true + provenance: false tags: | ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} ${{ env.IMAGE_NAME }}:latest diff --git a/.github/workflows/build-opencode-router.yml b/.github/workflows/build-opencode-router.yml index 84aca9ca8d75..aa26c1b4d011 100644 --- a/.github/workflows/build-opencode-router.yml +++ b/.github/workflows/build-opencode-router.yml @@ -48,6 +48,7 @@ jobs: file: packages/opencode-router/Dockerfile platforms: linux/amd64 push: true + provenance: false tags: | ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} ${{ env.IMAGE_NAME }}:latest From f2bc3fc6df13f516949996bca7a488fdf0c89152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 10:27:00 +0200 Subject: [PATCH 016/616] =?UTF-8?q?fix(ci):=20GitOps=20image=20tag=20propa?= =?UTF-8?q?gation=20=E2=80=94=20build=20commits=20tag=20to=20Pulumi=20conf?= =?UTF-8?q?ig=20before=20deploy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build workflows now update deployment/homelab/Pulumi.dev.yaml with the freshly built image tag and push the change [skip ci]. Deploy workflow triggers on both build workflows (router + cloudflare-operator) and deploys against the updated config, ensuring k8s always runs the image version that was just built. --- .github/workflows/build-cloudflare-operator.yml | 12 +++++++++++- .github/workflows/build-opencode-router.yml | 12 +++++++++++- .github/workflows/deploy-homelab.yml | 12 +++++++++--- deployment/homelab/Pulumi.dev.yaml | 2 +- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-cloudflare-operator.yml b/.github/workflows/build-cloudflare-operator.yml index a1e56cfbf6f5..5f7cfef0c858 100644 --- a/.github/workflows/build-cloudflare-operator.yml +++ b/.github/workflows/build-cloudflare-operator.yml @@ -17,7 +17,7 @@ on: default: true permissions: - contents: read + contents: write packages: write jobs: @@ -61,3 +61,13 @@ jobs: - name: Output image tag run: echo "Built and pushed ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}" + + - name: Update Pulumi stack config with new image tag + run: | + yq e '.config["code:cfOperatorImage"] = "${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}"' \ + -i deployment/homelab/Pulumi.dev.yaml + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add deployment/homelab/Pulumi.dev.yaml + git diff --cached --quiet || git commit -m "chore(deploy): update cfOperatorImage to ${{ steps.version.outputs.tag }} [skip ci]" + git push diff --git a/.github/workflows/build-opencode-router.yml b/.github/workflows/build-opencode-router.yml index aa26c1b4d011..3c8ddf7ef888 100644 --- a/.github/workflows/build-opencode-router.yml +++ b/.github/workflows/build-opencode-router.yml @@ -12,7 +12,7 @@ on: workflow_dispatch: permissions: - contents: read + contents: write packages: write jobs: @@ -57,3 +57,13 @@ jobs: - name: Output image tag run: echo "Built and pushed ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}" + + - name: Update Pulumi stack config with new image tag + run: | + yq e '.config["code:routerImage"] = "${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}"' \ + -i deployment/homelab/Pulumi.dev.yaml + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add deployment/homelab/Pulumi.dev.yaml + git diff --cached --quiet || git commit -m "chore(deploy): update routerImage to ${{ steps.version.outputs.tag }} [skip ci]" + git push diff --git a/.github/workflows/deploy-homelab.yml b/.github/workflows/deploy-homelab.yml index 90e078731adb..5cd8b36eb4d5 100644 --- a/.github/workflows/deploy-homelab.yml +++ b/.github/workflows/deploy-homelab.yml @@ -7,6 +7,11 @@ name: Deploy to homelab # - kubeconfig setup # - pulumi up / preview # +# GitOps flow: +# 1. build-opencode-router.yml or build-cloudflare-operator.yml builds the image +# 2. The build workflow commits the new image tag to deployment/homelab/Pulumi.dev.yaml +# 3. This workflow triggers via workflow_run and deploys using the updated config +# # Pulumi project: "opencode" (Pulumi.yaml name: opencode) # Stack: mrsimpson/opencode/dev # Base infra read via: StackReference("mrsimpson/homelab/dev") @@ -17,9 +22,10 @@ name: Deploy to homelab # PULUMI_ACCESS_TOKEN — Pulumi Cloud access token on: - # Run after images are built + # Run after images are built and Pulumi config is updated workflow_run: workflows: + - "Build opencode-router image" - "Build opencode-cloudflare-operator image" types: - completed @@ -44,8 +50,8 @@ permissions: jobs: # Optional pre-step: update image tags in Pulumi stack config before deploying. - # Only runs when image overrides are provided via workflow_dispatch. - # Skipped entirely on workflow_run triggers (images are already baked into config). + # Only runs when image overrides are provided via workflow_dispatch (emergency override). + # On workflow_run triggers, Pulumi.dev.yaml is already updated by the build workflow. update-image-config: name: Update image config runs-on: ubuntu-latest diff --git a/deployment/homelab/Pulumi.dev.yaml b/deployment/homelab/Pulumi.dev.yaml index 80ff00cc90f2..4d41ef8e4427 100644 --- a/deployment/homelab/Pulumi.dev.yaml +++ b/deployment/homelab/Pulumi.dev.yaml @@ -1,5 +1,5 @@ config: - code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-local.2e87b1a6b.dirty + code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-main.7871982 code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-local.2e87b1a6b.dirty code:opencodeImage: ghcr.io/mrsimpson/opencode:1.3.13-local.2e87b1a6b.dirty code:homelabStack: mrsimpson/homelab/dev From b7a8126a9fc8e87e88c571bd2233495435166795 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Apr 2026 08:43:55 +0000 Subject: [PATCH 017/616] chore(deploy): update routerImage to 0.0.1-main.f2bc3fc [skip ci] --- deployment/homelab/Pulumi.dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/homelab/Pulumi.dev.yaml b/deployment/homelab/Pulumi.dev.yaml index 4d41ef8e4427..f4971582e72e 100644 --- a/deployment/homelab/Pulumi.dev.yaml +++ b/deployment/homelab/Pulumi.dev.yaml @@ -1,5 +1,5 @@ config: - code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-main.7871982 + code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-main.f2bc3fc code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-local.2e87b1a6b.dirty code:opencodeImage: ghcr.io/mrsimpson/opencode:1.3.13-local.2e87b1a6b.dirty code:homelabStack: mrsimpson/homelab/dev From 019b689d37f47d1fb80276981bacc7b4d2125cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 10:47:36 +0200 Subject: [PATCH 018/616] fix(ci): pull --rebase before pushing Pulumi config update to avoid concurrent push rejection --- .beads/issues.jsonl | 17 +++++++++++ .beads/last-touched | 2 +- .../workflows/build-cloudflare-operator.yml | 14 +++++---- .github/workflows/build-opencode-router.yml | 14 +++++---- ...opencode-make-fork-independent-hlqprz.json | 29 +++++++++++++++++++ ...opencode-preconfigure-opencode-2z76q4.json | 29 +++++++++++++++++++ 6 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 .vibe/beads-state-opencode-make-fork-independent-hlqprz.json create mode 100644 .vibe/beads-state-opencode-preconfigure-opencode-2z76q4.json diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index b4336dcb0117..cc57d248c8d4 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -61,3 +61,20 @@ {"id":"opencode-2.3.8","title":"Extend pod-manager.ts: add config-init initContainer (idempotent: seeds /home/opencode/.config/opencode from /etc/opencode-defaults only if not present)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T11:07:59.703187+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T12:38:25.57131+02:00","closed_at":"2026-04-13T12:38:25.57131+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.8","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} {"id":"opencode-2.3.9","title":"Replace workflowAgents with .env file sourced from ConfigMap","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T11:56:37.4858+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T11:58:27.066402+02:00","closed_at":"2026-04-13T11:58:27.066402+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-2.3.9","depends_on_id":"opencode-2.3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} {"id":"opencode-2.4","title":"Commit","description":"Ensure code quality and documentation accuracy through systematic cleanup and review. **STEP 1: Code Cleanup** Systematically clean up development artifacts: 1. **Remove Debug Output**: Search for and remove all temporary debug output statements used during development. Look for language-specific debug output methods (console logging, print statements, debug output functions). Remove any debugging statements that were added for development purposes. 2. **Review TODO/FIXME Comments**: - Address each TODO/FIXME comment by either implementing the solution or documenting why it's deferred - Remove completed TODOs - Convert remaining TODOs to proper issue tracking if needed 3. **Remove Debugging Code Blocks**: - Remove temporary debugging code, test code blocks, and commented-out code - Clean up any experimental code that's no longer needed - Ensure proper error handling replaces temporary debug logging **STEP 2: Documentation Review** Review and update documentation to reflect final implementation: 1. **Update Long-Term Memory Documents**: Based on what was actually implemented: - If exists: Update it if requirements changed during development - If exists: Update it if architectural impacts were identified - If exists: Update it if design details were refined or changed - Otherwise: Document any changes in the plan file 2. **Compare Against Implementation**: Review documentation against actual implemented functionality 3. **Update Changed Sections**: Only modify documentation sections that have functional changes 4. **Remove Development Progress**: Remove references to development iterations, progress notes, and temporary decisions 5. **Focus on Final State**: Ensure documentation describes the final implemented state, not the development process 6. **Ask User to Review Document Updates** **STEP 3: Final Validation** - Run existing tests to ensure cleanup didn't break functionality - Verify documentation accuracy with a final review - Ensure code is ready for production/delivery Update task progress and mark completed work as you finalize the feature.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T10:20:51.579228+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T10:20:51.579228+02:00","dependencies":[{"issue_id":"opencode-2.4","depends_on_id":"opencode-2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"opencode-2.4","depends_on_id":"opencode-2.3","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3","title":"opencode: epcc (development-plan-make-fork-independent.md)","description":"Responsible vibe engineering session using epcc workflow for opencode","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:18:09.412595+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T18:18:09.412595+02:00"} +{"id":"opencode-3.1","title":"Explore","description":"Research the codebase to understand existing patterns and gather context about the problem space. - If uncertain about conventions or rules, ask the user about them - Read relevant files and documentation - If exists: Understand and document requirements there - Otherwise: Document requirements in your task management system Focus on understanding without writing code yet. Document your findings and create tasks as needed.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:18:09.633542+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T18:18:09.633542+02:00","dependencies":[{"issue_id":"opencode-3.1","depends_on_id":"opencode-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.1.1","title":"Audit all 35 upstream workflows for fork impact","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:23:34.792246+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T18:23:42.216423+02:00","closed_at":"2026-04-13T18:23:42.216423+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-3.1.1","depends_on_id":"opencode-3.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.1.2","title":"Identify fork package CI gaps (router, router-app not in turbo)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:23:34.992272+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T18:23:42.34199+02:00","closed_at":"2026-04-13T18:23:42.34199+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-3.1.2","depends_on_id":"opencode-3.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.1.3","title":"Identify upstream workflow modifications needed (disable/guard)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:23:35.165775+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T18:23:42.474047+02:00","closed_at":"2026-04-13T18:23:42.474047+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-3.1.3","depends_on_id":"opencode-3.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.1.4","title":"Audit dev branch references across fork-owned files","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:31:05.292682+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T18:31:05.447663+02:00","closed_at":"2026-04-13T18:31:05.447663+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-3.1.4","depends_on_id":"opencode-3.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.2","title":"Plan","description":"Create a detailed implementation strategy based on your exploration: - If exists: Base your strategy on requirements from it - Otherwise: Use existing task context Break down the work into specific, actionable tasks. Consider edge cases, dependencies, and potential challenges. - If architectural changes needed and exists: Document in - Otherwise: Create tasks to track architectural decisions - If exists: Adhere to the design in it - Otherwise: Elaborate design options and present them to the user Document the planning work thoroughly and create implementation tasks as part of the code phase as needed.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:18:09.802841+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T18:18:09.802841+02:00","dependencies":[{"issue_id":"opencode-3.2","depends_on_id":"opencode-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"opencode-3.2","depends_on_id":"opencode-3.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.2.1","title":"Revert AI-agent noise from packages/opencode/package.json","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:37:28.287121+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T19:27:28.323876+02:00","closed_at":"2026-04-13T19:27:28.323876+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-3.2.1","depends_on_id":"opencode-3.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.2.2","title":"Create .github/fork/disable-upstream-workflows.sh script","description":"DRY script that documents and disables all upstream workflows that are irrelevant or harmful for this fork using gh workflow disable. No upstream files touched.","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:37:35.769531+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T19:27:28.477372+02:00","closed_at":"2026-04-13T19:27:28.477372+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-3.2.2","depends_on_id":"opencode-3.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.2.3","title":"Run disable script to deactivate upstream workflows on fork repo","description":"Execute .github/fork/disable-upstream-workflows.sh to actually disable the workflows via GitHub API","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:37:35.970124+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T19:27:28.606187+02:00","closed_at":"2026-04-13T19:27:28.606187+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-3.2.3","depends_on_id":"opencode-3.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.2.4","title":"Add fork-specific CI workflow for opencode-router and opencode-router-app validation","description":"New .github/workflows/fork-ci.yml that typecheck/tests fork packages (opencode-router via pnpm, opencode-router-app via bun)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:37:42.8327+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T19:27:28.753558+02:00","closed_at":"2026-04-13T19:27:28.753558+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-3.2.4","depends_on_id":"opencode-3.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.2.5","title":"Update fork-owned workflow branch triggers from dev to main","description":"Update build-opencode-router.yml, build-cloudflare-operator.yml, deploy-homelab.yml to trigger on main instead of dev","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:37:43.056012+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T19:27:28.90186+02:00","closed_at":"2026-04-13T19:27:28.90186+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-3.2.5","depends_on_id":"opencode-3.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.2.6","title":"Create FORK.md contributor guide","description":"Documents fork structure, packages, upstream merge procedure, branch strategy, and CI management (how to re-run disable script after merges)","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:37:49.567927+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T19:27:29.020367+02:00","closed_at":"2026-04-13T19:27:29.020367+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-3.2.6","depends_on_id":"opencode-3.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.2.7","title":"Add fork extensions section to top of README.md","description":"Prepend minimal section above upstream logo block linking to FORK.md and each fork package. Survives upstream merges.","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:37:49.76013+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T19:27:29.164453+02:00","closed_at":"2026-04-13T19:27:29.164453+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-3.2.7","depends_on_id":"opencode-3.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.2.8","title":"Rename fork default branch dev to main (git + GitHub)","description":"git branch -m dev main, push, update GitHub default branch setting, keep dev as local tracking ref for upstream/dev","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:37:56.770957+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T19:27:29.277116+02:00","closed_at":"2026-04-13T19:27:29.277116+02:00","close_reason":"Closed","dependencies":[{"issue_id":"opencode-3.2.8","depends_on_id":"opencode-3.2","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.3","title":"Code","description":"Follow your plan to build the solution: - If exists: Follow the design from it - Otherwise: Elaborate design options and present them to the user - If exists: Build according to the architecture from it - Otherwise: Elaborate architectural options and present them to the user - If exists: Ensure requirements from it are met - Otherwise: Ensure existing requirements are met based on your task context Write clean, well-structured code with proper error handling. Prevent regression by building, linting, and executing existing tests. Stay flexible and adapt the plan as you learn more during implementation. Update task progress and create new tasks as needed.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:18:10.00882+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T18:18:10.00882+02:00","dependencies":[{"issue_id":"opencode-3.3","depends_on_id":"opencode-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"opencode-3.3","depends_on_id":"opencode-3.2","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]} +{"id":"opencode-3.4","title":"Commit","description":"Ensure code quality and documentation accuracy through systematic cleanup and review. **STEP 1: Code Cleanup** Systematically clean up development artifacts: 1. **Remove Debug Output**: Search for and remove all temporary debug output statements used during development. Look for language-specific debug output methods (console logging, print statements, debug output functions). Remove any debugging statements that were added for development purposes. 2. **Review TODO/FIXME Comments**: - Address each TODO/FIXME comment by either implementing the solution or documenting why it's deferred - Remove completed TODOs - Convert remaining TODOs to proper issue tracking if needed 3. **Remove Debugging Code Blocks**: - Remove temporary debugging code, test code blocks, and commented-out code - Clean up any experimental code that's no longer needed - Ensure proper error handling replaces temporary debug logging **STEP 2: Documentation Review** Review and update documentation to reflect final implementation: 1. **Update Long-Term Memory Documents**: Based on what was actually implemented: - If exists: Update it if requirements changed during development - If exists: Update it if architectural impacts were identified - If exists: Update it if design details were refined or changed - Otherwise: Document any changes in the plan file 2. **Compare Against Implementation**: Review documentation against actual implemented functionality 3. **Update Changed Sections**: Only modify documentation sections that have functional changes 4. **Remove Development Progress**: Remove references to development iterations, progress notes, and temporary decisions 5. **Focus on Final State**: Ensure documentation describes the final implemented state, not the development process 6. **Ask User to Review Document Updates** **STEP 3: Final Validation** - Run existing tests to ensure cleanup didn't break functionality - Verify documentation accuracy with a final review - Ensure code is ready for production/delivery Update task progress and mark completed work as you finalize the feature.","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-13T18:18:10.200895+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-13T18:18:10.200895+02:00","dependencies":[{"issue_id":"opencode-3.4","depends_on_id":"opencode-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"opencode-3.4","depends_on_id":"opencode-3.3","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]} diff --git a/.beads/last-touched b/.beads/last-touched index 401299168e74..25339970d1e0 100644 --- a/.beads/last-touched +++ b/.beads/last-touched @@ -1 +1 @@ -opencode-2.3.12 +opencode-3.2.8 diff --git a/.github/workflows/build-cloudflare-operator.yml b/.github/workflows/build-cloudflare-operator.yml index 5f7cfef0c858..d92a2e6a73b1 100644 --- a/.github/workflows/build-cloudflare-operator.yml +++ b/.github/workflows/build-cloudflare-operator.yml @@ -64,10 +64,14 @@ jobs: - name: Update Pulumi stack config with new image tag run: | - yq e '.config["code:cfOperatorImage"] = "${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}"' \ - -i deployment/homelab/Pulumi.dev.yaml git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add deployment/homelab/Pulumi.dev.yaml - git diff --cached --quiet || git commit -m "chore(deploy): update cfOperatorImage to ${{ steps.version.outputs.tag }} [skip ci]" - git push + for i in 1 2 3; do + git pull --rebase origin main + yq e '.config["code:cfOperatorImage"] = "${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}"' \ + -i deployment/homelab/Pulumi.dev.yaml + git add deployment/homelab/Pulumi.dev.yaml + git diff --cached --quiet && break + git commit -m "chore(deploy): update cfOperatorImage to ${{ steps.version.outputs.tag }} [skip ci]" + git push && break || git reset HEAD~1 + done diff --git a/.github/workflows/build-opencode-router.yml b/.github/workflows/build-opencode-router.yml index 3c8ddf7ef888..26b0054c265a 100644 --- a/.github/workflows/build-opencode-router.yml +++ b/.github/workflows/build-opencode-router.yml @@ -60,10 +60,14 @@ jobs: - name: Update Pulumi stack config with new image tag run: | - yq e '.config["code:routerImage"] = "${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}"' \ - -i deployment/homelab/Pulumi.dev.yaml git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add deployment/homelab/Pulumi.dev.yaml - git diff --cached --quiet || git commit -m "chore(deploy): update routerImage to ${{ steps.version.outputs.tag }} [skip ci]" - git push + for i in 1 2 3; do + git pull --rebase origin main + yq e '.config["code:routerImage"] = "${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}"' \ + -i deployment/homelab/Pulumi.dev.yaml + git add deployment/homelab/Pulumi.dev.yaml + git diff --cached --quiet && break + git commit -m "chore(deploy): update routerImage to ${{ steps.version.outputs.tag }} [skip ci]" + git push && break || git reset HEAD~1 + done diff --git a/.vibe/beads-state-opencode-make-fork-independent-hlqprz.json b/.vibe/beads-state-opencode-make-fork-independent-hlqprz.json new file mode 100644 index 000000000000..096ca09e0c1b --- /dev/null +++ b/.vibe/beads-state-opencode-make-fork-independent-hlqprz.json @@ -0,0 +1,29 @@ +{ + "conversationId": "opencode-make-fork-independent-hlqprz", + "projectPath": "/Users/oliverjaegle/projects/open-source/opencode", + "epicId": "opencode-3", + "phaseTasks": [ + { + "phaseId": "explore", + "phaseName": "Explore", + "taskId": "opencode-3.1" + }, + { + "phaseId": "plan", + "phaseName": "Plan", + "taskId": "opencode-3.2" + }, + { + "phaseId": "code", + "phaseName": "Code", + "taskId": "opencode-3.3" + }, + { + "phaseId": "commit", + "phaseName": "Commit", + "taskId": "opencode-3.4" + } + ], + "createdAt": "2026-04-13T16:18:10.639Z", + "updatedAt": "2026-04-13T16:18:10.639Z" +} \ No newline at end of file diff --git a/.vibe/beads-state-opencode-preconfigure-opencode-2z76q4.json b/.vibe/beads-state-opencode-preconfigure-opencode-2z76q4.json new file mode 100644 index 000000000000..32004879e9c6 --- /dev/null +++ b/.vibe/beads-state-opencode-preconfigure-opencode-2z76q4.json @@ -0,0 +1,29 @@ +{ + "conversationId": "opencode-preconfigure-opencode-2z76q4", + "projectPath": "/Users/oliverjaegle/projects/open-source/opencode", + "epicId": "opencode-2", + "phaseTasks": [ + { + "phaseId": "explore", + "phaseName": "Explore", + "taskId": "opencode-2.1" + }, + { + "phaseId": "plan", + "phaseName": "Plan", + "taskId": "opencode-2.2" + }, + { + "phaseId": "code", + "phaseName": "Code", + "taskId": "opencode-2.3" + }, + { + "phaseId": "commit", + "phaseName": "Commit", + "taskId": "opencode-2.4" + } + ], + "createdAt": "2026-04-13T08:20:52.045Z", + "updatedAt": "2026-04-13T08:20:52.045Z" +} \ No newline at end of file From 02b65e2f799662d955b1d43dec89b54baee3109a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Apr 2026 08:55:22 +0000 Subject: [PATCH 019/616] chore(deploy): update cfOperatorImage to 0.1.0-main.019b689 [skip ci] --- deployment/homelab/Pulumi.dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/homelab/Pulumi.dev.yaml b/deployment/homelab/Pulumi.dev.yaml index f4971582e72e..3301c933b4c2 100644 --- a/deployment/homelab/Pulumi.dev.yaml +++ b/deployment/homelab/Pulumi.dev.yaml @@ -1,6 +1,6 @@ config: code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-main.f2bc3fc - code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-local.2e87b1a6b.dirty + code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-main.019b689 code:opencodeImage: ghcr.io/mrsimpson/opencode:1.3.13-local.2e87b1a6b.dirty code:homelabStack: mrsimpson/homelab/dev code:anthropicApiKey: From a9c1ab333c2130c2a294774e022ea53efca94762 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Apr 2026 08:59:39 +0000 Subject: [PATCH 020/616] chore(deploy): update routerImage to 0.0.1-main.019b689 [skip ci] --- deployment/homelab/Pulumi.dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/homelab/Pulumi.dev.yaml b/deployment/homelab/Pulumi.dev.yaml index 3301c933b4c2..2509f735fa22 100644 --- a/deployment/homelab/Pulumi.dev.yaml +++ b/deployment/homelab/Pulumi.dev.yaml @@ -1,5 +1,5 @@ config: - code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-main.f2bc3fc + code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-main.019b689 code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-main.019b689 code:opencodeImage: ghcr.io/mrsimpson/opencode:1.3.13-local.2e87b1a6b.dirty code:homelabStack: mrsimpson/homelab/dev From 142292f3747a762bcd2bdcae7ee94edd86299a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 12:08:19 +0200 Subject: [PATCH 021/616] feat(ci): build custom opencode image in CI, pin base to fork version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add build-opencode-image.yml workflow — builds deployment/homelab/images/opencode/ using BASE_VERSION=v{packages/opencode/package.json version} so the fork always runs the exact upstream opencode version present in this repo, not floating :latest. Dockerfile updated to accept ARG BASE_VERSION (defaults to 'latest' for local builds). Deploy workflow triggers on the new build workflow too. Stale Pulumi.dev.yaml tags updated to match the actual built SHAs. --- .github/workflows/build-opencode-image.yml | 80 +++++++++++++++++++ .github/workflows/deploy-homelab.yml | 6 +- deployment/homelab/Pulumi.dev.yaml | 6 +- deployment/homelab/images/opencode/Dockerfile | 5 +- 4 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/build-opencode-image.yml diff --git a/.github/workflows/build-opencode-image.yml b/.github/workflows/build-opencode-image.yml new file mode 100644 index 000000000000..b1fe6a6c7821 --- /dev/null +++ b/.github/workflows/build-opencode-image.yml @@ -0,0 +1,80 @@ +name: Build opencode image + +# Builds the fork-customised opencode image that bakes in default config, tools, +# and MCP servers on top of the upstream ghcr.io/anomalyco/opencode base. +# +# Base image is pinned to the version in packages/opencode/package.json so the +# fork always runs the exact opencode version present in this repo. + +on: + push: + branches: + - main + paths: + - deployment/homelab/images/opencode/** + - packages/opencode/package.json + - .github/workflows/build-opencode-image.yml + workflow_dispatch: + +permissions: + contents: write + packages: write + +jobs: + build: + runs-on: ubuntu-latest + env: + IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/opencode + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Get version from package.json + id: version + run: | + VERSION=$(node -p "require('./packages/opencode/package.json').version") + SHA_SHORT=${GITHUB_SHA::7} + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "tag=${VERSION}-${GITHUB_REF_NAME}.${SHA_SHORT}" >> $GITHUB_OUTPUT + echo "base=v${VERSION}" >> $GITHUB_OUTPUT + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: deployment/homelab/images/opencode + platforms: linux/amd64 + push: true + provenance: false + build-args: | + BASE_VERSION=${{ steps.version.outputs.base }} + tags: | + ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} + ${{ env.IMAGE_NAME }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Output image tag + run: echo "Built and pushed ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}" + + - name: Update Pulumi stack config with new image tag + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + for i in 1 2 3; do + git pull --rebase origin main + yq e '.config["code:opencodeImage"] = "${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}"' \ + -i deployment/homelab/Pulumi.dev.yaml + git add deployment/homelab/Pulumi.dev.yaml + git diff --cached --quiet && break + git commit -m "chore(deploy): update opencodeImage to ${{ steps.version.outputs.tag }} [skip ci]" + git push && break || git reset HEAD~1 + done diff --git a/.github/workflows/deploy-homelab.yml b/.github/workflows/deploy-homelab.yml index 5cd8b36eb4d5..b76e6229cb1b 100644 --- a/.github/workflows/deploy-homelab.yml +++ b/.github/workflows/deploy-homelab.yml @@ -8,9 +8,8 @@ name: Deploy to homelab # - pulumi up / preview # # GitOps flow: -# 1. build-opencode-router.yml or build-cloudflare-operator.yml builds the image -# 2. The build workflow commits the new image tag to deployment/homelab/Pulumi.dev.yaml -# 3. This workflow triggers via workflow_run and deploys using the updated config +# 1. Any build-*.yml workflow builds the image and commits the new tag to Pulumi.dev.yaml +# 2. This workflow triggers via workflow_run and deploys using the updated config # # Pulumi project: "opencode" (Pulumi.yaml name: opencode) # Stack: mrsimpson/opencode/dev @@ -27,6 +26,7 @@ on: workflows: - "Build opencode-router image" - "Build opencode-cloudflare-operator image" + - "Build opencode image" types: - completed branches: diff --git a/deployment/homelab/Pulumi.dev.yaml b/deployment/homelab/Pulumi.dev.yaml index 2509f735fa22..0534602a10e0 100644 --- a/deployment/homelab/Pulumi.dev.yaml +++ b/deployment/homelab/Pulumi.dev.yaml @@ -1,7 +1,7 @@ config: - code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-main.019b689 - code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-main.019b689 - code:opencodeImage: ghcr.io/mrsimpson/opencode:1.3.13-local.2e87b1a6b.dirty + code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-main.f2bc3fc + code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-main.f2bc3fc + code:opencodeImage: ghcr.io/mrsimpson/opencode:1.3.13-main.f2bc3fc code:homelabStack: mrsimpson/homelab/dev code:anthropicApiKey: secure: AAABAG/9eXalNNef/hxPr5yv8F9phJnv6c/p7GVpY8Rnutzox59WLEkwutX7RtF7djYUqqXHf7jRoR7PYDkf0Kx1dr8OF+coy/u3i/S+Cz3DpQzfwQrhRCyx/AtSC3AIUiteAfzlxK/UDAse2MabbOuk8W733qZVhFuBDkqxYgm3Du9kFZD97anwz1Y= diff --git a/deployment/homelab/images/opencode/Dockerfile b/deployment/homelab/images/opencode/Dockerfile index 3b017377227e..d7bd89aa5f03 100644 --- a/deployment/homelab/images/opencode/Dockerfile +++ b/deployment/homelab/images/opencode/Dockerfile @@ -1,4 +1,7 @@ -FROM ghcr.io/anomalyco/opencode +# BASE_VERSION is injected by CI from packages/opencode/package.json (e.g. "v1.3.13"). +# Locally defaults to "latest" — override with: docker build --build-arg BASE_VERSION=v1.3.13 +ARG BASE_VERSION=latest +FROM ghcr.io/anomalyco/opencode:${BASE_VERSION} # Install tools as root before switching to the non-root user: # git, curl, bash — essential dev tools From fe00af0bcc538e8a850cdc14f52babe776ad96b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 12:14:42 +0200 Subject: [PATCH 022/616] feat(ci): add upstream-merge workflow to sync anomalyco/opencode into fork via PR --- .github/fork/disable-upstream-workflows.sh | 2 +- .github/workflows/fork-validate.yml | 2 +- .github/workflows/upstream-merge.yml | 181 ++++++++++++++++++ .../development-plan-make-fork-independent.md | 13 ++ 4 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/upstream-merge.yml diff --git a/.github/fork/disable-upstream-workflows.sh b/.github/fork/disable-upstream-workflows.sh index 64f1d431678a..0ae0be1500db 100755 --- a/.github/fork/disable-upstream-workflows.sh +++ b/.github/fork/disable-upstream-workflows.sh @@ -82,7 +82,7 @@ echo " deploy-homelab.yml — fork homelab deployment" echo " pr-standards.yml — conventional commit enforcement (kept)" echo " publish.yml — already guarded: if: repo == 'anomalyco/opencode'" echo " stats.yml — already guarded: if: repo == 'anomalyco/opencode'" -echo " docs-update.yml — already guarded: if: repo == 'sst/opencode'" +echo " docs-update.yml — already guarded: if: repo == 'anomalyco/opencode'" echo " notify-discord.yml — release-only trigger, harmless" echo " sync-zed-extension.yml — release-only trigger, harmless" echo " publish-github-action.yml — tag-only trigger, harmless" diff --git a/.github/workflows/fork-validate.yml b/.github/workflows/fork-validate.yml index e604b840ef57..b122acc8ab62 100644 --- a/.github/workflows/fork-validate.yml +++ b/.github/workflows/fork-validate.yml @@ -104,7 +104,7 @@ jobs: - name: Fetch upstream run: | - git remote add upstream https://github.com/sst/opencode.git + git remote add upstream https://github.com/anomalyco/opencode.git git fetch upstream dev --depth=50 - name: Check for upstream file modifications diff --git a/.github/workflows/upstream-merge.yml b/.github/workflows/upstream-merge.yml new file mode 100644 index 000000000000..2f2fbc3eba77 --- /dev/null +++ b/.github/workflows/upstream-merge.yml @@ -0,0 +1,181 @@ +name: Merge upstream into fork + +# Manually triggered workflow that fetches the latest upstream commits and +# opens a pull-request against the fork's default branch (main). +# +# Usage: +# gh workflow run upstream-merge.yml \ +# [-f upstream_ref=dev] \ +# [-f target_branch=main] +# +# What it does: +# 1. Fetch upstream (https://github.com/anomalyco/opencode) +# 2. Create a branch upstream-merge/- +# 3. Merge upstream/ with --no-ff (keeps history clean) +# If conflicts exist the merge is left in a conflicted state and the +# branch is pushed — the PR description will flag this so the reviewer +# knows conflicts need manual resolution before merging. +# 4. Push the branch and open a PR with a summary of new upstream commits +# and a reminder to re-run the disable-upstream-workflows.sh script. +# +# Required permissions: contents: write + pull-requests: write + +on: + workflow_dispatch: + inputs: + upstream_ref: + description: "Upstream branch / tag / SHA to merge" + default: dev + required: true + type: string + target_branch: + description: "Fork branch to merge into" + default: main + required: true + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + merge: + name: Merge ${{ inputs.upstream_ref }} → ${{ inputs.target_branch }} + runs-on: ubuntu-latest + + steps: + - name: Checkout fork + uses: actions/checkout@v4 + with: + ref: ${{ inputs.target_branch }} + fetch-depth: 0 + + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Add upstream remote and fetch + run: | + git remote add upstream https://github.com/anomalyco/opencode.git + git fetch upstream ${{ inputs.upstream_ref }} + + - name: Resolve merge-base and collect new commits + id: info + run: | + BASE=$(git merge-base HEAD upstream/${{ inputs.upstream_ref }}) + echo "base=$BASE" >> $GITHUB_OUTPUT + COUNT=$(git rev-list --count "$BASE"..upstream/${{ inputs.upstream_ref }}) + echo "count=$COUNT" >> $GITHUB_OUTPUT + UPSTREAM_SHA=$(git rev-parse --short upstream/${{ inputs.upstream_ref }}) + echo "upstream_sha=$UPSTREAM_SHA" >> $GITHUB_OUTPUT + DATE=$(date +%Y%m%d) + echo "date=$DATE" >> $GITHUB_OUTPUT + BRANCH="upstream-merge/${{ inputs.upstream_ref }}-${DATE}" + echo "branch=$BRANCH" >> $GITHUB_OUTPUT + if [ "$COUNT" -eq 0 ]; then + echo "up_to_date=true" >> $GITHUB_OUTPUT + else + echo "up_to_date=false" >> $GITHUB_OUTPUT + fi + + - name: Exit early if already up-to-date + if: steps.info.outputs.up_to_date == 'true' + run: | + echo "Fork is already up-to-date with upstream/${{ inputs.upstream_ref }}. Nothing to do." + + - name: Create merge branch from target + if: steps.info.outputs.up_to_date == 'false' + run: git checkout -b ${{ steps.info.outputs.branch }} + + - name: Attempt merge + id: merge + if: steps.info.outputs.up_to_date == 'false' + run: | + set +e + git merge --no-ff upstream/${{ inputs.upstream_ref }} \ + -m "chore: merge upstream/${{ inputs.upstream_ref }} @ ${{ steps.info.outputs.upstream_sha }}" + STATUS=$? + set -e + if [ $STATUS -ne 0 ]; then + echo "has_conflicts=true" >> $GITHUB_OUTPUT + # Stage conflicted state so we can push the branch + git add -A + git commit --no-edit -m "chore: merge upstream (CONFLICTS — resolve before merging PR)" + else + echo "has_conflicts=false" >> $GITHUB_OUTPUT + fi + + - name: Push merge branch + if: steps.info.outputs.up_to_date == 'false' + run: git push origin ${{ steps.info.outputs.branch }} + + - name: Collect upstream commit log + id: log + if: steps.info.outputs.up_to_date == 'false' + run: | + LOG=$(git log --oneline "${{ steps.info.outputs.base }}"..upstream/${{ inputs.upstream_ref }} | head -50) + # Escape for multiline GitHub output + EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) + echo "log<<$EOF" >> $GITHUB_OUTPUT + echo "$LOG" >> $GITHUB_OUTPUT + echo "$EOF" >> $GITHUB_OUTPUT + + - name: Create pull request + if: steps.info.outputs.up_to_date == 'false' + uses: actions/github-script@v7 + with: + script: | + const hasConflicts = '${{ steps.merge.outputs.has_conflicts }}' === 'true' + const count = '${{ steps.info.outputs.count }}' + const upstreamSha = '${{ steps.info.outputs.upstream_sha }}' + const log = `${{ steps.log.outputs.log }}` + + const conflictWarning = hasConflicts ? [ + '## ⚠️ Merge conflicts detected', + '', + 'This branch was pushed with unresolved merge conflicts (committed with conflict markers).', + 'You **must resolve the conflicts** before merging this PR.', + '', + ] : [] + + const body = [ + ...conflictWarning, + '## Summary', + '', + `Merges **${count} commit(s)** from \`upstream/${{ inputs.upstream_ref }}\` @ \`${upstreamSha}\` into \`${{ inputs.target_branch }}\`.`, + '', + '## Upstream commits', + '', + '```', + log, + '```', + '', + '## Post-merge checklist', + '', + '- [ ] Resolve any merge conflicts', + '- [ ] Re-run the disable-upstream-workflows script to catch any newly added upstream workflows:', + ' ```sh', + ' bash .github/fork/disable-upstream-workflows.sh', + ' ```', + '- [ ] Check `deployment/homelab/images/opencode/Dockerfile` — if the upstream `packages/opencode` version changed, update `BASE_VERSION` in the build-opencode-image workflow or bump `packages/opencode/package.json`', + '- [ ] Run fork validation locally: `cd packages/opencode-router && pnpm test`', + ].join('\n') + + const upstreamRef = '${{ inputs.upstream_ref }}' + const title = hasConflicts + ? `chore: merge upstream/${upstreamRef} @ ${upstreamSha} (CONFLICTS)` + : `chore: merge upstream/${upstreamRef} @ ${upstreamSha} (${count} commits)` + + const pr = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title, + body, + head: '${{ steps.info.outputs.branch }}', + base: '${{ inputs.target_branch }}', + }) + + console.log(`PR created: ${pr.data.html_url}`) + core.summary.addRaw(`## PR created\n\n${pr.data.html_url}`) + await core.summary.write() diff --git a/.vibe/development-plan-make-fork-independent.md b/.vibe/development-plan-make-fork-independent.md index d30ac9d5c98b..893654544037 100644 --- a/.vibe/development-plan-make-fork-independent.md +++ b/.vibe/development-plan-make-fork-independent.md @@ -61,6 +61,19 @@ Optimize this fork of `sst/opencode` (now `anomalyco/opencode`) so that: - This section survives merges if kept minimal (upstream adds to the file's interior; a top-of-file prepend rarely conflicts) - Section links to `FORK.md` and each fork package's own README +### Upstream URL + +Use `https://github.com/anomalyco/opencode.git` (canonical) everywhere — `sst/opencode` redirects to this but using the canonical URL avoids ambiguity. Updated in `upstream-merge.yml`, `fork-validate.yml`, `disable-upstream-workflows.sh`, and the local `upstream` remote. + +### Upstream merge workflow (`upstream-merge.yml`) + +- `workflow_dispatch` only — never runs automatically +- Inputs: `upstream_ref` (default `dev`), `target_branch` (default `main`) +- Creates branch `upstream-merge/-`, merges with `--no-ff` +- If merge conflicts: commits with conflict markers and flags PR title/body — human must resolve before merging +- Early-exit guard: all non-info steps gated on `up_to_date == 'false'` so a no-op run is safe +- PR body includes: conflict warning (if any), commit count + SHA, last 50 upstream commit log, post-merge checklist (re-run disable script, check Dockerfile base version, run fork validation) + ## Notes ### Repo context From 91bbd584c2f642bf3ada4e4b95aa915b0b523ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 12:15:52 +0200 Subject: [PATCH 023/616] fix(ci): add workflows permission to upstream-merge to allow pushing workflow file changes --- .github/workflows/upstream-merge.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/upstream-merge.yml b/.github/workflows/upstream-merge.yml index 2f2fbc3eba77..ed337ea50784 100644 --- a/.github/workflows/upstream-merge.yml +++ b/.github/workflows/upstream-merge.yml @@ -37,6 +37,7 @@ on: permissions: contents: write pull-requests: write + workflows: write jobs: merge: From b6cb08ff8a12c3c4b84dc10a34d1dd596d3deade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 12:57:47 +0200 Subject: [PATCH 024/616] fix(ci): use GH_PAT for upstream-merge push to allow workflow file changes --- .github/workflows/upstream-merge.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/upstream-merge.yml b/.github/workflows/upstream-merge.yml index ed337ea50784..099b6b4cd58c 100644 --- a/.github/workflows/upstream-merge.yml +++ b/.github/workflows/upstream-merge.yml @@ -19,6 +19,8 @@ name: Merge upstream into fork # and a reminder to re-run the disable-upstream-workflows.sh script. # # Required permissions: contents: write + pull-requests: write +# Required secret: GH_PAT — classic PAT with repo + workflow scopes +# (GITHUB_TOKEN cannot push branches containing workflow file changes) on: workflow_dispatch: @@ -37,7 +39,6 @@ on: permissions: contents: write pull-requests: write - workflows: write jobs: merge: @@ -50,6 +51,8 @@ jobs: with: ref: ${{ inputs.target_branch }} fetch-depth: 0 + # PAT needed so the push can include upstream workflow file changes + token: ${{ secrets.GH_PAT }} - name: Configure git identity run: | @@ -126,6 +129,7 @@ jobs: if: steps.info.outputs.up_to_date == 'false' uses: actions/github-script@v7 with: + github-token: ${{ secrets.GH_PAT }} script: | const hasConflicts = '${{ steps.merge.outputs.has_conflicts }}' === 'true' const count = '${{ steps.info.outputs.count }}' From 4f02879a0c967e42647d1e8f4b2d2ab4892cbc86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 12:59:41 +0200 Subject: [PATCH 025/616] fix(ci): skip compatibility-guard on upstream-merge/* PRs --- .github/workflows/fork-validate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fork-validate.yml b/.github/workflows/fork-validate.yml index b122acc8ab62..bceaa424e106 100644 --- a/.github/workflows/fork-validate.yml +++ b/.github/workflows/fork-validate.yml @@ -95,7 +95,7 @@ jobs: compatibility-guard: name: Compatibility guard runs-on: ubuntu-latest - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && !startsWith(github.head_ref, 'upstream-merge/') steps: - uses: actions/checkout@v4 From f63d1607aa6e4a527fda8f04b263421fccb95156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 13:01:28 +0200 Subject: [PATCH 026/616] chore(ci): opt into Node.js 24 for all fork workflows --- .github/workflows/build-cloudflare-operator.yml | 3 +++ .github/workflows/build-opencode-image.yml | 3 +++ .github/workflows/build-opencode-router.yml | 3 +++ .github/workflows/deploy-homelab.yml | 3 +++ .github/workflows/fork-validate.yml | 3 +++ .github/workflows/upstream-merge.yml | 3 +++ 6 files changed, 18 insertions(+) diff --git a/.github/workflows/build-cloudflare-operator.yml b/.github/workflows/build-cloudflare-operator.yml index d92a2e6a73b1..36892fde4bd5 100644 --- a/.github/workflows/build-cloudflare-operator.yml +++ b/.github/workflows/build-cloudflare-operator.yml @@ -20,6 +20,9 @@ permissions: contents: write packages: write +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/build-opencode-image.yml b/.github/workflows/build-opencode-image.yml index b1fe6a6c7821..b62d68230215 100644 --- a/.github/workflows/build-opencode-image.yml +++ b/.github/workflows/build-opencode-image.yml @@ -20,6 +20,9 @@ permissions: contents: write packages: write +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/build-opencode-router.yml b/.github/workflows/build-opencode-router.yml index 26b0054c265a..68247e2105c1 100644 --- a/.github/workflows/build-opencode-router.yml +++ b/.github/workflows/build-opencode-router.yml @@ -15,6 +15,9 @@ permissions: contents: write packages: write +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/deploy-homelab.yml b/.github/workflows/deploy-homelab.yml index b76e6229cb1b..38ffe26dea3d 100644 --- a/.github/workflows/deploy-homelab.yml +++ b/.github/workflows/deploy-homelab.yml @@ -48,6 +48,9 @@ on: permissions: contents: read +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: # Optional pre-step: update image tags in Pulumi stack config before deploying. # Only runs when image overrides are provided via workflow_dispatch (emergency override). diff --git a/.github/workflows/fork-validate.yml b/.github/workflows/fork-validate.yml index bceaa424e106..0b40f5ac4f9f 100644 --- a/.github/workflows/fork-validate.yml +++ b/.github/workflows/fork-validate.yml @@ -21,6 +21,9 @@ permissions: contents: read pull-requests: write +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: validate-router: name: Validate opencode-router diff --git a/.github/workflows/upstream-merge.yml b/.github/workflows/upstream-merge.yml index 099b6b4cd58c..f548bcfdc275 100644 --- a/.github/workflows/upstream-merge.yml +++ b/.github/workflows/upstream-merge.yml @@ -40,6 +40,9 @@ permissions: contents: write pull-requests: write +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: merge: name: Merge ${{ inputs.upstream_ref }} → ${{ inputs.target_branch }} From 4a75c8696f9e03aee3e3988c75109ab31171c6a8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Apr 2026 11:01:52 +0000 Subject: [PATCH 027/616] chore(deploy): update routerImage to 0.0.1-main.f63d160 [skip ci] --- deployment/homelab/Pulumi.dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/homelab/Pulumi.dev.yaml b/deployment/homelab/Pulumi.dev.yaml index 0534602a10e0..19c5a1fcb95c 100644 --- a/deployment/homelab/Pulumi.dev.yaml +++ b/deployment/homelab/Pulumi.dev.yaml @@ -1,5 +1,5 @@ config: - code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-main.f2bc3fc + code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-main.f63d160 code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-main.f2bc3fc code:opencodeImage: ghcr.io/mrsimpson/opencode:1.3.13-main.f2bc3fc code:homelabStack: mrsimpson/homelab/dev From d9595612970e91225af7d62259c45eaae651cd3b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Apr 2026 11:02:37 +0000 Subject: [PATCH 028/616] chore(deploy): update cfOperatorImage to 0.1.0-main.f63d160 [skip ci] --- deployment/homelab/Pulumi.dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/homelab/Pulumi.dev.yaml b/deployment/homelab/Pulumi.dev.yaml index 19c5a1fcb95c..5870a3feffd4 100644 --- a/deployment/homelab/Pulumi.dev.yaml +++ b/deployment/homelab/Pulumi.dev.yaml @@ -1,6 +1,6 @@ config: code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-main.f63d160 - code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-main.f2bc3fc + code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-main.f63d160 code:opencodeImage: ghcr.io/mrsimpson/opencode:1.3.13-main.f2bc3fc code:homelabStack: mrsimpson/homelab/dev code:anthropicApiKey: From c92b32aafa9d1ce8f41842fbae73b58e1161ef63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 13:16:22 +0200 Subject: [PATCH 029/616] fix(ci): resolve upstream base image tag by commit timestamp instead of semver --- .github/workflows/build-opencode-image.yml | 60 ++++++++++++++++++++-- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-opencode-image.yml b/.github/workflows/build-opencode-image.yml index b62d68230215..4bf480a293b0 100644 --- a/.github/workflows/build-opencode-image.yml +++ b/.github/workflows/build-opencode-image.yml @@ -3,8 +3,12 @@ name: Build opencode image # Builds the fork-customised opencode image that bakes in default config, tools, # and MCP servers on top of the upstream ghcr.io/anomalyco/opencode base. # -# Base image is pinned to the version in packages/opencode/package.json so the -# fork always runs the exact opencode version present in this repo. +# Base image tag resolution: +# Upstream publishes tags in the format 0.0.0-dev-YYYYMMDDHHMI (UTC). +# We find the earliest tag whose embedded timestamp is >= the author date of +# the most recent upstream commit reachable from HEAD (i.e. the last merge from +# upstream/dev). This gives us the image that was built from that exact commit +# (or the very next one), without requiring OCI labels or package metadata. on: push: @@ -28,8 +32,11 @@ jobs: runs-on: ubuntu-latest env: IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/opencode + UPSTREAM_IMAGE: ghcr.io/anomalyco/opencode steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -41,14 +48,57 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Get version from package.json + - name: Resolve base image tag and fork image tag id: version run: | + # --- Fork image tag (our own output tag) --- VERSION=$(node -p "require('./packages/opencode/package.json').version") SHA_SHORT=${GITHUB_SHA::7} echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "tag=${VERSION}-${GITHUB_REF_NAME}.${SHA_SHORT}" >> $GITHUB_OUTPUT - echo "base=v${VERSION}" >> $GITHUB_OUTPUT + + # --- Upstream base image tag resolution --- + # Find the latest upstream commit reachable from HEAD by checking the + # merge-base with upstream/dev. Use its author date to find the right + # 0.0.0-dev-YYYYMMDDHHMI tag on GHCR. + git remote add upstream https://github.com/anomalyco/opencode.git + git fetch upstream dev --depth=1 + + UPSTREAM_SHA=$(git merge-base HEAD upstream/dev) + # Author date of that commit (UTC), formatted as the tag timestamp pattern + COMMIT_TS=$(git log -1 --format="%ad" --date=format:"%Y%m%d%H%M" "$UPSTREAM_SHA") + echo "upstream commit: $UPSTREAM_SHA commit_ts: $COMMIT_TS" + + # Fetch all GHCR tags for anomalyco/opencode (paginated) + ANON_TOKEN=$(curl -s \ + "https://ghcr.io/token?scope=repository:anomalyco/opencode:pull&service=ghcr.io" \ + | jq -r '.token') + + ALL_TAGS="" + LAST="" + while true; do + URL="https://ghcr.io/v2/anomalyco/opencode/tags/list?n=100${LAST:+&last=$LAST}" + PAGE=$(curl -s -H "Authorization: Bearer $ANON_TOKEN" "$URL") + TAGS=$(echo "$PAGE" | jq -r '.tags[]') + ALL_TAGS="${ALL_TAGS}${TAGS}"$'\n' + COUNT=$(echo "$TAGS" | wc -l) + LAST=$(echo "$TAGS" | tail -1) + [ "$COUNT" -lt 100 ] && break + done + + # Filter to 0.0.0-dev-* tags, sort ascending, find first one >= commit_ts + BASE_TAG=$(echo "$ALL_TAGS" \ + | grep -E '^0\.0\.0-dev-[0-9]{12}$' \ + | sort \ + | awk -v ts="$COMMIT_TS" 'substr($0,11) >= ts {print; exit}') + + if [ -z "$BASE_TAG" ]; then + echo "No dev tag found >= $COMMIT_TS — falling back to latest" >&2 + BASE_TAG="latest" + fi + + echo "Resolved base image tag: $BASE_TAG" + echo "base=${BASE_TAG}" >> $GITHUB_OUTPUT - name: Build and push uses: docker/build-push-action@v6 @@ -66,7 +116,7 @@ jobs: cache-to: type=gha,mode=max - name: Output image tag - run: echo "Built and pushed ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}" + run: echo "Built and pushed ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} (base: ${{ steps.version.outputs.base }})" - name: Update Pulumi stack config with new image tag run: | From 5df30ab8c3cbfa56ef83d047c54a9ba7d21336a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 13:16:48 +0200 Subject: [PATCH 030/616] docs(dockerfile): update BASE_VERSION comment to reflect timestamp-based tag scheme --- deployment/homelab/images/opencode/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deployment/homelab/images/opencode/Dockerfile b/deployment/homelab/images/opencode/Dockerfile index d7bd89aa5f03..dc124d3c1791 100644 --- a/deployment/homelab/images/opencode/Dockerfile +++ b/deployment/homelab/images/opencode/Dockerfile @@ -1,5 +1,6 @@ -# BASE_VERSION is injected by CI from packages/opencode/package.json (e.g. "v1.3.13"). -# Locally defaults to "latest" — override with: docker build --build-arg BASE_VERSION=v1.3.13 +# BASE_VERSION is resolved by CI to the upstream 0.0.0-dev-YYYYMMDDHHMI tag +# matching the upstream commit at the time of the last upstream merge. +# Locally defaults to "latest" — override with: docker build --build-arg BASE_VERSION=0.0.0-dev-202604030013 ARG BASE_VERSION=latest FROM ghcr.io/anomalyco/opencode:${BASE_VERSION} From c09a3a741ff74b40e9b074371e2d19cf9c9c127f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 13:32:27 +0200 Subject: [PATCH 031/616] fix(ci): use block scalar for run step to avoid YAML quoting issue --- .github/workflows/build-opencode-image.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-opencode-image.yml b/.github/workflows/build-opencode-image.yml index 4bf480a293b0..46eddc8c7bec 100644 --- a/.github/workflows/build-opencode-image.yml +++ b/.github/workflows/build-opencode-image.yml @@ -116,7 +116,8 @@ jobs: cache-to: type=gha,mode=max - name: Output image tag - run: echo "Built and pushed ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} (base: ${{ steps.version.outputs.base }})" + run: | + echo "Built and pushed ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} (base: ${{ steps.version.outputs.base }})" - name: Update Pulumi stack config with new image tag run: | From a7822d1d3e323c47f980cdce4caf3d13f428242d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 13:34:00 +0200 Subject: [PATCH 032/616] fix(ci): fix merge-base shell structure and increase upstream fetch depth --- .github/workflows/build-opencode-image.yml | 73 ++++++++++++---------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/.github/workflows/build-opencode-image.yml b/.github/workflows/build-opencode-image.yml index 46eddc8c7bec..52f92ab85ea0 100644 --- a/.github/workflows/build-opencode-image.yml +++ b/.github/workflows/build-opencode-image.yml @@ -58,43 +58,48 @@ jobs: echo "tag=${VERSION}-${GITHUB_REF_NAME}.${SHA_SHORT}" >> $GITHUB_OUTPUT # --- Upstream base image tag resolution --- - # Find the latest upstream commit reachable from HEAD by checking the - # merge-base with upstream/dev. Use its author date to find the right - # 0.0.0-dev-YYYYMMDDHHMI tag on GHCR. + # Find the merge-base between HEAD and upstream/dev to get the upstream + # commit SHA that was merged in. Checkout is full-depth so local history + # is complete; we only need the tip of upstream/dev to resolve the SHA. git remote add upstream https://github.com/anomalyco/opencode.git - git fetch upstream dev --depth=1 - - UPSTREAM_SHA=$(git merge-base HEAD upstream/dev) - # Author date of that commit (UTC), formatted as the tag timestamp pattern - COMMIT_TS=$(git log -1 --format="%ad" --date=format:"%Y%m%d%H%M" "$UPSTREAM_SHA") - echo "upstream commit: $UPSTREAM_SHA commit_ts: $COMMIT_TS" - - # Fetch all GHCR tags for anomalyco/opencode (paginated) - ANON_TOKEN=$(curl -s \ - "https://ghcr.io/token?scope=repository:anomalyco/opencode:pull&service=ghcr.io" \ - | jq -r '.token') - - ALL_TAGS="" - LAST="" - while true; do - URL="https://ghcr.io/v2/anomalyco/opencode/tags/list?n=100${LAST:+&last=$LAST}" - PAGE=$(curl -s -H "Authorization: Bearer $ANON_TOKEN" "$URL") - TAGS=$(echo "$PAGE" | jq -r '.tags[]') - ALL_TAGS="${ALL_TAGS}${TAGS}"$'\n' - COUNT=$(echo "$TAGS" | wc -l) - LAST=$(echo "$TAGS" | tail -1) - [ "$COUNT" -lt 100 ] && break - done - - # Filter to 0.0.0-dev-* tags, sort ascending, find first one >= commit_ts - BASE_TAG=$(echo "$ALL_TAGS" \ - | grep -E '^0\.0\.0-dev-[0-9]{12}$' \ - | sort \ - | awk -v ts="$COMMIT_TS" 'substr($0,11) >= ts {print; exit}') + git fetch upstream dev --depth=50 - if [ -z "$BASE_TAG" ]; then - echo "No dev tag found >= $COMMIT_TS — falling back to latest" >&2 + UPSTREAM_SHA=$(git merge-base HEAD upstream/dev || true) + if [ -z "$UPSTREAM_SHA" ]; then + echo "merge-base not found — falling back to latest" >&2 BASE_TAG="latest" + else + # Author date of that commit (UTC), formatted as the tag timestamp pattern + COMMIT_TS=$(git log -1 --format="%ad" --date=format:"%Y%m%d%H%M" "$UPSTREAM_SHA") + echo "upstream commit: $UPSTREAM_SHA commit_ts: $COMMIT_TS" + + # Fetch all GHCR tags for anomalyco/opencode (paginated) + ANON_TOKEN=$(curl -s \ + "https://ghcr.io/token?scope=repository:anomalyco/opencode:pull&service=ghcr.io" \ + | jq -r '.token') + + ALL_TAGS="" + LAST="" + while true; do + URL="https://ghcr.io/v2/anomalyco/opencode/tags/list?n=100${LAST:+&last=$LAST}" + PAGE=$(curl -s -H "Authorization: Bearer $ANON_TOKEN" "$URL") + TAGS=$(echo "$PAGE" | jq -r '.tags[]') + ALL_TAGS="${ALL_TAGS}${TAGS}"$'\n' + COUNT=$(echo "$TAGS" | wc -l) + LAST=$(echo "$TAGS" | tail -1) + [ "$COUNT" -lt 100 ] && break + done + + # Filter to 0.0.0-dev-* tags, sort ascending, find first one >= commit_ts + BASE_TAG=$(echo "$ALL_TAGS" \ + | grep -E '^0\.0\.0-dev-[0-9]{12}$' \ + | sort \ + | awk -v ts="$COMMIT_TS" 'substr($0,11) >= ts {print; exit}') + + if [ -z "$BASE_TAG" ]; then + echo "No dev tag found >= $COMMIT_TS — falling back to latest" >&2 + BASE_TAG="latest" + fi fi echo "Resolved base image tag: $BASE_TAG" From 3b588eba14d91d44f43f95699ef024f5430aeabc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 13:38:05 +0200 Subject: [PATCH 033/616] fix(ci): pin base image via .base-version file, update on upstream merge --- .github/workflows/build-opencode-image.yml | 70 ++----------------- .github/workflows/upstream-merge.yml | 44 +++++++++++- .../homelab/images/opencode/.base-version | 1 + deployment/homelab/images/opencode/Dockerfile | 5 +- 4 files changed, 53 insertions(+), 67 deletions(-) create mode 100644 deployment/homelab/images/opencode/.base-version diff --git a/.github/workflows/build-opencode-image.yml b/.github/workflows/build-opencode-image.yml index 52f92ab85ea0..92b6c5b14628 100644 --- a/.github/workflows/build-opencode-image.yml +++ b/.github/workflows/build-opencode-image.yml @@ -3,12 +3,10 @@ name: Build opencode image # Builds the fork-customised opencode image that bakes in default config, tools, # and MCP servers on top of the upstream ghcr.io/anomalyco/opencode base. # -# Base image tag resolution: -# Upstream publishes tags in the format 0.0.0-dev-YYYYMMDDHHMI (UTC). -# We find the earliest tag whose embedded timestamp is >= the author date of -# the most recent upstream commit reachable from HEAD (i.e. the last merge from -# upstream/dev). This gives us the image that was built from that exact commit -# (or the very next one), without requiring OCI labels or package metadata. +# Base image tag: +# Pinned in deployment/homelab/images/opencode/.base-version (format: 0.0.0-dev-YYYYMMDDHHMI). +# Updated automatically by the upstream-merge workflow when upstream is merged. +# Upstream publishes images as 0.0.0-dev-YYYYMMDDHHMI — not semver. on: push: @@ -16,7 +14,6 @@ on: - main paths: - deployment/homelab/images/opencode/** - - packages/opencode/package.json - .github/workflows/build-opencode-image.yml workflow_dispatch: @@ -32,11 +29,8 @@ jobs: runs-on: ubuntu-latest env: IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/opencode - UPSTREAM_IMAGE: ghcr.io/anomalyco/opencode steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -48,62 +42,16 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Resolve base image tag and fork image tag + - name: Read versions id: version run: | - # --- Fork image tag (our own output tag) --- VERSION=$(node -p "require('./packages/opencode/package.json').version") SHA_SHORT=${GITHUB_SHA::7} + BASE_TAG=$(cat deployment/homelab/images/opencode/.base-version | tr -d '[:space:]') echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "tag=${VERSION}-${GITHUB_REF_NAME}.${SHA_SHORT}" >> $GITHUB_OUTPUT - - # --- Upstream base image tag resolution --- - # Find the merge-base between HEAD and upstream/dev to get the upstream - # commit SHA that was merged in. Checkout is full-depth so local history - # is complete; we only need the tip of upstream/dev to resolve the SHA. - git remote add upstream https://github.com/anomalyco/opencode.git - git fetch upstream dev --depth=50 - - UPSTREAM_SHA=$(git merge-base HEAD upstream/dev || true) - if [ -z "$UPSTREAM_SHA" ]; then - echo "merge-base not found — falling back to latest" >&2 - BASE_TAG="latest" - else - # Author date of that commit (UTC), formatted as the tag timestamp pattern - COMMIT_TS=$(git log -1 --format="%ad" --date=format:"%Y%m%d%H%M" "$UPSTREAM_SHA") - echo "upstream commit: $UPSTREAM_SHA commit_ts: $COMMIT_TS" - - # Fetch all GHCR tags for anomalyco/opencode (paginated) - ANON_TOKEN=$(curl -s \ - "https://ghcr.io/token?scope=repository:anomalyco/opencode:pull&service=ghcr.io" \ - | jq -r '.token') - - ALL_TAGS="" - LAST="" - while true; do - URL="https://ghcr.io/v2/anomalyco/opencode/tags/list?n=100${LAST:+&last=$LAST}" - PAGE=$(curl -s -H "Authorization: Bearer $ANON_TOKEN" "$URL") - TAGS=$(echo "$PAGE" | jq -r '.tags[]') - ALL_TAGS="${ALL_TAGS}${TAGS}"$'\n' - COUNT=$(echo "$TAGS" | wc -l) - LAST=$(echo "$TAGS" | tail -1) - [ "$COUNT" -lt 100 ] && break - done - - # Filter to 0.0.0-dev-* tags, sort ascending, find first one >= commit_ts - BASE_TAG=$(echo "$ALL_TAGS" \ - | grep -E '^0\.0\.0-dev-[0-9]{12}$' \ - | sort \ - | awk -v ts="$COMMIT_TS" 'substr($0,11) >= ts {print; exit}') - - if [ -z "$BASE_TAG" ]; then - echo "No dev tag found >= $COMMIT_TS — falling back to latest" >&2 - BASE_TAG="latest" - fi - fi - - echo "Resolved base image tag: $BASE_TAG" echo "base=${BASE_TAG}" >> $GITHUB_OUTPUT + echo "Building fork image ${VERSION}-${GITHUB_REF_NAME}.${SHA_SHORT} on top of upstream ${BASE_TAG}" - name: Build and push uses: docker/build-push-action@v6 @@ -120,10 +68,6 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - - name: Output image tag - run: | - echo "Built and pushed ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} (base: ${{ steps.version.outputs.base }})" - - name: Update Pulumi stack config with new image tag run: | git config user.name "github-actions[bot]" diff --git a/.github/workflows/upstream-merge.yml b/.github/workflows/upstream-merge.yml index f548bcfdc275..f9b2194dd359 100644 --- a/.github/workflows/upstream-merge.yml +++ b/.github/workflows/upstream-merge.yml @@ -113,6 +113,48 @@ jobs: echo "has_conflicts=false" >> $GITHUB_OUTPUT fi + - name: Update base image version pin + if: steps.info.outputs.up_to_date == 'false' && steps.merge.outputs.has_conflicts == 'false' + id: baseversion + run: | + # Find the 0.0.0-dev-* GHCR tag for anomalyco/opencode that corresponds + # to the upstream commit we just merged. Tag format is YYYYMMDDHHMI (UTC). + UPSTREAM_SHA="${{ steps.info.outputs.upstream_sha }}" + COMMIT_TS=$(git log -1 --format="%ad" --date=format:"%Y%m%d%H%M" \ + "$(git rev-parse upstream/${{ inputs.upstream_ref }})") + + ANON_TOKEN=$(curl -s \ + "https://ghcr.io/token?scope=repository:anomalyco/opencode:pull&service=ghcr.io" \ + | jq -r '.token') + + ALL_TAGS="" LAST="" + while true; do + URL="https://ghcr.io/v2/anomalyco/opencode/tags/list?n=100${LAST:+&last=$LAST}" + PAGE=$(curl -s -H "Authorization: Bearer $ANON_TOKEN" "$URL") + TAGS=$(echo "$PAGE" | jq -r '.tags[]') + ALL_TAGS="${ALL_TAGS}${TAGS}"$'\n' + COUNT=$(echo "$TAGS" | wc -l) + LAST=$(echo "$TAGS" | tail -1) + [ "$COUNT" -lt 100 ] && break + done + + BASE_TAG=$(echo "$ALL_TAGS" \ + | grep -E '^0\.0\.0-dev-[0-9]{12}$' \ + | sort \ + | awk -v ts="$COMMIT_TS" 'substr($0,11) >= ts {print; exit}') + + if [ -z "$BASE_TAG" ]; then + echo "No dev tag found >= $COMMIT_TS — using latest" >&2 + BASE_TAG=$(echo "$ALL_TAGS" | grep -E '^0\.0\.0-dev-[0-9]{12}$' | sort | tail -1) + fi + + echo "Resolved base tag: $BASE_TAG (upstream commit ts: $COMMIT_TS)" + echo "$BASE_TAG" > deployment/homelab/images/opencode/.base-version + echo "base_tag=${BASE_TAG}" >> $GITHUB_OUTPUT + + git add deployment/homelab/images/opencode/.base-version + git commit --amend --no-edit + - name: Push merge branch if: steps.info.outputs.up_to_date == 'false' run: git push origin ${{ steps.info.outputs.branch }} @@ -166,7 +208,7 @@ jobs: ' ```sh', ' bash .github/fork/disable-upstream-workflows.sh', ' ```', - '- [ ] Check `deployment/homelab/images/opencode/Dockerfile` — if the upstream `packages/opencode` version changed, update `BASE_VERSION` in the build-opencode-image workflow or bump `packages/opencode/package.json`', + '- [ ] `.base-version` has been auto-updated — verify it points to the right upstream image tag', '- [ ] Run fork validation locally: `cd packages/opencode-router && pnpm test`', ].join('\n') diff --git a/deployment/homelab/images/opencode/.base-version b/deployment/homelab/images/opencode/.base-version new file mode 100644 index 000000000000..0b34f4ef5d90 --- /dev/null +++ b/deployment/homelab/images/opencode/.base-version @@ -0,0 +1 @@ +0.0.0-dev-202604140703 diff --git a/deployment/homelab/images/opencode/Dockerfile b/deployment/homelab/images/opencode/Dockerfile index dc124d3c1791..7f9342770dd4 100644 --- a/deployment/homelab/images/opencode/Dockerfile +++ b/deployment/homelab/images/opencode/Dockerfile @@ -1,6 +1,5 @@ -# BASE_VERSION is resolved by CI to the upstream 0.0.0-dev-YYYYMMDDHHMI tag -# matching the upstream commit at the time of the last upstream merge. -# Locally defaults to "latest" — override with: docker build --build-arg BASE_VERSION=0.0.0-dev-202604030013 +# BASE_VERSION is read from .base-version by CI and updated by the upstream-merge workflow. +# Locally defaults to "latest" — override with: docker build --build-arg BASE_VERSION=0.0.0-dev-202604140703 ARG BASE_VERSION=latest FROM ghcr.io/anomalyco/opencode:${BASE_VERSION} From 692a2aff5fa8e7e687e214f083a5f863b044b4b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 13:40:33 +0200 Subject: [PATCH 034/616] fix(ci): use GH_PAT for GHCR login to create new opencode package --- .github/workflows/build-opencode-image.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-opencode-image.yml b/.github/workflows/build-opencode-image.yml index 92b6c5b14628..ddf6bfccb152 100644 --- a/.github/workflows/build-opencode-image.yml +++ b/.github/workflows/build-opencode-image.yml @@ -40,7 +40,8 @@ jobs: with: registry: ghcr.io username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} + # GH_PAT needed to push to a new GHCR package (GITHUB_TOKEN cannot create packages) + password: ${{ secrets.GH_PAT }} - name: Read versions id: version From d913f769a97cf584a3071940d784e976d973dc6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 13:42:06 +0200 Subject: [PATCH 035/616] fix(ci): remove GHA cache from opencode image build to avoid cross-repo blob mount 403 --- .github/workflows/build-opencode-image.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build-opencode-image.yml b/.github/workflows/build-opencode-image.yml index ddf6bfccb152..cc28aa6e475b 100644 --- a/.github/workflows/build-opencode-image.yml +++ b/.github/workflows/build-opencode-image.yml @@ -66,8 +66,6 @@ jobs: tags: | ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} ${{ env.IMAGE_NAME }}:latest - cache-from: type=gha - cache-to: type=gha,mode=max - name: Update Pulumi stack config with new image tag run: | From 99d4c8c567a5d84ce97eda9a00f6e3b1a26d0d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 13:44:19 +0200 Subject: [PATCH 036/616] fix(ci): add pull:true to force re-download base layers and avoid cross-owner blob mount 403 --- .github/workflows/build-opencode-image.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-opencode-image.yml b/.github/workflows/build-opencode-image.yml index cc28aa6e475b..4cfddef2d982 100644 --- a/.github/workflows/build-opencode-image.yml +++ b/.github/workflows/build-opencode-image.yml @@ -61,6 +61,7 @@ jobs: platforms: linux/amd64 push: true provenance: false + pull: true build-args: | BASE_VERSION=${{ steps.version.outputs.base }} tags: | From eb7119d89f634a45429950d6fb992acb94304e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 13:46:26 +0200 Subject: [PATCH 037/616] fix(ci): pass GH_PAT as github-token to build-push-action for buildx daemon auth --- .github/workflows/build-opencode-image.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-opencode-image.yml b/.github/workflows/build-opencode-image.yml index 4cfddef2d982..50db5eea4cf7 100644 --- a/.github/workflows/build-opencode-image.yml +++ b/.github/workflows/build-opencode-image.yml @@ -62,6 +62,7 @@ jobs: push: true provenance: false pull: true + github-token: ${{ secrets.GH_PAT }} build-args: | BASE_VERSION=${{ steps.version.outputs.base }} tags: | From 64391855ce357c422b06867e4835dc213eaf50b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 13:48:02 +0200 Subject: [PATCH 038/616] fix(ci): switch opencode image build to plain docker build+push to avoid buildx cross-owner blob mount 403 --- .github/workflows/build-opencode-image.yml | 42 ++++++++++------------ 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build-opencode-image.yml b/.github/workflows/build-opencode-image.yml index 50db5eea4cf7..51185ffa39b5 100644 --- a/.github/workflows/build-opencode-image.yml +++ b/.github/workflows/build-opencode-image.yml @@ -7,6 +7,9 @@ name: Build opencode image # Pinned in deployment/homelab/images/opencode/.base-version (format: 0.0.0-dev-YYYYMMDDHHMI). # Updated automatically by the upstream-merge workflow when upstream is merged. # Upstream publishes images as 0.0.0-dev-YYYYMMDDHHMI — not semver. +# +# Note: uses plain `docker build` + `docker push` (not buildx) to avoid +# GHCR cross-owner blob mount 403 errors that occur with buildx on new packages. on: push: @@ -32,16 +35,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - # GH_PAT needed to push to a new GHCR package (GITHUB_TOKEN cannot create packages) - password: ${{ secrets.GH_PAT }} + run: | + echo "${{ secrets.GH_PAT }}" | docker login ghcr.io \ + -u ${{ github.repository_owner }} --password-stdin - name: Read versions id: version @@ -54,20 +51,19 @@ jobs: echo "base=${BASE_TAG}" >> $GITHUB_OUTPUT echo "Building fork image ${VERSION}-${GITHUB_REF_NAME}.${SHA_SHORT} on top of upstream ${BASE_TAG}" - - name: Build and push - uses: docker/build-push-action@v6 - with: - context: deployment/homelab/images/opencode - platforms: linux/amd64 - push: true - provenance: false - pull: true - github-token: ${{ secrets.GH_PAT }} - build-args: | - BASE_VERSION=${{ steps.version.outputs.base }} - tags: | - ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} - ${{ env.IMAGE_NAME }}:latest + - name: Build + run: | + docker build \ + --build-arg BASE_VERSION=${{ steps.version.outputs.base }} \ + --pull \ + -t ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} \ + -t ${{ env.IMAGE_NAME }}:latest \ + deployment/homelab/images/opencode + + - name: Push + run: | + docker push ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} + docker push ${{ env.IMAGE_NAME }}:latest - name: Update Pulumi stack config with new image tag run: | From 758fe048a3a3e55dd5303993b0206adb7cff6284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 13:49:04 +0200 Subject: [PATCH 039/616] fix(ci): use GITHUB_TOKEN for docker login (has packages:write), PAT lacks write:packages --- .github/workflows/build-opencode-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-opencode-image.yml b/.github/workflows/build-opencode-image.yml index 51185ffa39b5..fb3284448250 100644 --- a/.github/workflows/build-opencode-image.yml +++ b/.github/workflows/build-opencode-image.yml @@ -37,7 +37,7 @@ jobs: - name: Login to GHCR run: | - echo "${{ secrets.GH_PAT }}" | docker login ghcr.io \ + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io \ -u ${{ github.repository_owner }} --password-stdin - name: Read versions From 70499c4a90d7620243d27cf443d6b17b608e6c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Apr 2026 13:50:14 +0200 Subject: [PATCH 040/616] fix(ci): use GH_PAT for GHCR login (needs write:packages scope) --- .github/workflows/build-opencode-image.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-opencode-image.yml b/.github/workflows/build-opencode-image.yml index fb3284448250..808a959a0d98 100644 --- a/.github/workflows/build-opencode-image.yml +++ b/.github/workflows/build-opencode-image.yml @@ -37,7 +37,8 @@ jobs: - name: Login to GHCR run: | - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io \ + # GH_PAT must have scopes: repo, workflow, write:packages + echo "${{ secrets.GH_PAT }}" | docker login ghcr.io \ -u ${{ github.repository_owner }} --password-stdin - name: Read versions From 323e63b9276be7126b8bf5673d6be1b8d010d46b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Apr 2026 12:19:57 +0000 Subject: [PATCH 041/616] chore(deploy): update opencodeImage to 1.3.13-main.70499c4 [skip ci] --- deployment/homelab/Pulumi.dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/homelab/Pulumi.dev.yaml b/deployment/homelab/Pulumi.dev.yaml index 5870a3feffd4..15b89c6706e1 100644 --- a/deployment/homelab/Pulumi.dev.yaml +++ b/deployment/homelab/Pulumi.dev.yaml @@ -1,7 +1,7 @@ config: code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-main.f63d160 code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-main.f63d160 - code:opencodeImage: ghcr.io/mrsimpson/opencode:1.3.13-main.f2bc3fc + code:opencodeImage: ghcr.io/mrsimpson/opencode:1.3.13-main.70499c4 code:homelabStack: mrsimpson/homelab/dev code:anthropicApiKey: secure: AAABAG/9eXalNNef/hxPr5yv8F9phJnv6c/p7GVpY8Rnutzox59WLEkwutX7RtF7djYUqqXHf7jRoR7PYDkf0Kx1dr8OF+coy/u3i/S+Cz3DpQzfwQrhRCyx/AtSC3AIUiteAfzlxK/UDAse2MabbOuk8W733qZVhFuBDkqxYgm3Du9kFZD97anwz1Y= From 4efb8034c9cceeac375d6b69a792fae79a335ec2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Apr 2026 12:22:13 +0000 Subject: [PATCH 042/616] chore(deploy): update opencodeImage to 1.3.13-main.323e63b [skip ci] --- deployment/homelab/Pulumi.dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/homelab/Pulumi.dev.yaml b/deployment/homelab/Pulumi.dev.yaml index 15b89c6706e1..d2553e1c61af 100644 --- a/deployment/homelab/Pulumi.dev.yaml +++ b/deployment/homelab/Pulumi.dev.yaml @@ -1,7 +1,7 @@ config: code:routerImage: ghcr.io/mrsimpson/opencode-router:0.0.1-main.f63d160 code:cfOperatorImage: ghcr.io/mrsimpson/opencode-cloudflare-operator:0.1.0-main.f63d160 - code:opencodeImage: ghcr.io/mrsimpson/opencode:1.3.13-main.70499c4 + code:opencodeImage: ghcr.io/mrsimpson/opencode:1.3.13-main.323e63b code:homelabStack: mrsimpson/homelab/dev code:anthropicApiKey: secure: AAABAG/9eXalNNef/hxPr5yv8F9phJnv6c/p7GVpY8Rnutzox59WLEkwutX7RtF7djYUqqXHf7jRoR7PYDkf0Kx1dr8OF+coy/u3i/S+Cz3DpQzfwQrhRCyx/AtSC3AIUiteAfzlxK/UDAse2MabbOuk8W733qZVhFuBDkqxYgm3Du9kFZD97anwz1Y= From 5eafc9bc9be27a894fb3c6e010d5fc8e3dba1b27 Mon Sep 17 00:00:00 2001 From: ykswang Date: Fri, 3 Apr 2026 08:13:10 +0800 Subject: [PATCH 043/616] fix(opencode): honor model limit.input overrides (#16306) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/opencode/src/provider/provider.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 81479dcd75c2..afd69f9e7072 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1100,6 +1100,7 @@ export namespace Provider { options: mergeDeep(existingModel?.options ?? {}, model.options ?? {}), limit: { context: model.limit?.context ?? existingModel?.limit?.context ?? 0, + input: model.limit?.input ?? existingModel?.limit?.input, output: model.limit?.output ?? existingModel?.limit?.output ?? 0, }, headers: mergeDeep(existingModel?.headers ?? {}, model.headers ?? {}), From 986ed3480f2c534c7b39e0be41e81b8701c82f0f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 2 Apr 2026 20:15:09 -0400 Subject: [PATCH 044/616] refactor(effect): prune unused facades (#20748) --- packages/opencode/src/account/index.ts | 5 - packages/opencode/src/command/index.ts | 36 ++-- packages/opencode/src/installation/index.ts | 4 - packages/opencode/src/mcp/auth.ts | 8 - packages/opencode/src/mcp/index.ts | 5 - packages/opencode/src/permission/index.ts | 13 +- packages/opencode/src/plugin/index.ts | 12 +- packages/opencode/src/pty/index.ts | 174 +++++++++--------- packages/opencode/src/question/index.ts | 11 +- packages/opencode/src/session/instruction.ts | 8 - packages/opencode/src/session/processor.ts | 2 +- packages/opencode/src/session/prompt.ts | 2 +- packages/opencode/src/session/summary.ts | 4 - packages/opencode/src/snapshot/index.ts | 4 - packages/opencode/src/tool/registry.ts | 4 - .../opencode/test/session/compaction.test.ts | 2 +- .../test/session/processor-effect.test.ts | 2 +- .../test/session/prompt-effect.test.ts | 2 +- .../test/session/snapshot-tool-race.test.ts | 2 +- 19 files changed, 134 insertions(+), 166 deletions(-) diff --git a/packages/opencode/src/account/index.ts b/packages/opencode/src/account/index.ts index bcc90b7b1d5c..2a8d35bfa8de 100644 --- a/packages/opencode/src/account/index.ts +++ b/packages/opencode/src/account/index.ts @@ -417,11 +417,6 @@ export namespace Account { return Option.getOrUndefined(await runPromise((service) => service.active())) } - export async function config(accountID: AccountID, orgID: OrgID): Promise | undefined> { - const cfg = await runPromise((service) => service.config(accountID, orgID)) - return Option.getOrUndefined(cfg) - } - export async function token(accountID: AccountID): Promise { const t = await runPromise((service) => service.token(accountID)) return Option.getOrUndefined(t) diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index f9bd594c1602..088d7c565975 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -124,20 +124,24 @@ export namespace Command { source: "mcp", description: prompt.description, get template() { - return new Promise(async (resolve, reject) => { - const template = await MCP.getPrompt( - prompt.client, - prompt.name, - prompt.arguments - ? Object.fromEntries(prompt.arguments.map((argument, i) => [argument.name, `$${i + 1}`])) - : {}, - ).catch(reject) - resolve( - template?.messages - .map((message) => (message.content.type === "text" ? message.content.text : "")) - .join("\n") || "", - ) - }) + return Effect.runPromise( + mcp + .getPrompt( + prompt.client, + prompt.name, + prompt.arguments + ? Object.fromEntries(prompt.arguments.map((argument, i) => [argument.name, `$${i + 1}`])) + : {}, + ) + .pipe( + Effect.map( + (template) => + template?.messages + .map((message) => (message.content.type === "text" ? message.content.text : "")) + .join("\n") || "", + ), + ), + ) }, hints: prompt.arguments?.map((_, i) => `$${i + 1}`) ?? [], } @@ -185,10 +189,6 @@ export namespace Command { const { runPromise } = makeRuntime(Service, defaultLayer) - export async function get(name: string) { - return runPromise((svc) => svc.get(name)) - } - export async function list() { return runPromise((svc) => svc.list()) } diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 232fa14f542c..f4cd4d09fa5f 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -341,10 +341,6 @@ export namespace Installation { const { runPromise } = makeRuntime(Service, defaultLayer) - export async function info(): Promise { - return runPromise((svc) => svc.info()) - } - export async function method(): Promise { return runPromise((svc) => svc.method()) } diff --git a/packages/opencode/src/mcp/auth.ts b/packages/opencode/src/mcp/auth.ts index 54f2ce495772..773ca0a6f523 100644 --- a/packages/opencode/src/mcp/auth.ts +++ b/packages/opencode/src/mcp/auth.ts @@ -168,14 +168,6 @@ export namespace McpAuth { export const updateCodeVerifier = async (mcpName: string, codeVerifier: string) => runPromise((svc) => svc.updateCodeVerifier(mcpName, codeVerifier)) - export const clearCodeVerifier = async (mcpName: string) => runPromise((svc) => svc.clearCodeVerifier(mcpName)) - export const updateOAuthState = async (mcpName: string, oauthState: string) => runPromise((svc) => svc.updateOAuthState(mcpName, oauthState)) - - export const getOAuthState = async (mcpName: string) => runPromise((svc) => svc.getOAuthState(mcpName)) - - export const clearOAuthState = async (mcpName: string) => runPromise((svc) => svc.clearOAuthState(mcpName)) - - export const isTokenExpired = async (mcpName: string) => runPromise((svc) => svc.isTokenExpired(mcpName)) } diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 47c39aad5646..8c92bb6b2e6d 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -889,8 +889,6 @@ export namespace MCP { export const status = async () => runPromise((svc) => svc.status()) - export const clients = async () => runPromise((svc) => svc.clients()) - export const tools = async () => runPromise((svc) => svc.tools()) export const prompts = async () => runPromise((svc) => svc.prompts()) @@ -906,9 +904,6 @@ export namespace MCP { export const getPrompt = async (clientName: string, name: string, args?: Record) => runPromise((svc) => svc.getPrompt(clientName, name, args)) - export const readResource = async (clientName: string, resourceUri: string) => - runPromise((svc) => svc.readResource(clientName, resourceUri)) - export const startAuth = async (mcpName: string) => runPromise((svc) => svc.startAuth(mcpName)) export const authenticate = async (mcpName: string) => runPromise((svc) => svc.authenticate(mcpName)) diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 1a7bd2c610a5..b2cc0f9bbc07 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -140,6 +140,7 @@ export namespace Permission { export const layer = Layer.effect( Service, Effect.gen(function* () { + const bus = yield* Bus.Service const state = yield* InstanceState.make( Effect.fn("Permission.state")(function* (ctx) { const row = Database.use((db) => @@ -191,7 +192,7 @@ export namespace Permission { const deferred = yield* Deferred.make() pending.set(id, { info, deferred }) - void Bus.publish(Event.Asked, info) + yield* bus.publish(Event.Asked, info) return yield* Effect.ensuring( Deferred.await(deferred), Effect.sync(() => { @@ -206,7 +207,7 @@ export namespace Permission { if (!existing) return pending.delete(input.requestID) - void Bus.publish(Event.Replied, { + yield* bus.publish(Event.Replied, { sessionID: existing.info.sessionID, requestID: existing.info.id, reply: input.reply, @@ -221,7 +222,7 @@ export namespace Permission { for (const [id, item] of pending.entries()) { if (item.info.sessionID !== existing.info.sessionID) continue pending.delete(id) - void Bus.publish(Event.Replied, { + yield* bus.publish(Event.Replied, { sessionID: item.info.sessionID, requestID: item.info.id, reply: "reject", @@ -249,7 +250,7 @@ export namespace Permission { ) if (!ok) continue pending.delete(id) - void Bus.publish(Event.Replied, { + yield* bus.publish(Event.Replied, { sessionID: item.info.sessionID, requestID: item.info.id, reply: "always", @@ -306,7 +307,9 @@ export namespace Permission { return result } - export const { runPromise } = makeRuntime(Service, layer) + export const defaultLayer = layer.pipe(Layer.provide(Bus.layer)) + + export const { runPromise } = makeRuntime(Service, defaultLayer) export async function ask(input: z.infer) { return runPromise((s) => s.ask(input)) diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 53a8741eac84..fb60fa096e88 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -74,8 +74,8 @@ export namespace Plugin { return result } - function publishPluginError(message: string) { - Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) + function publishPluginError(bus: Bus.Interface, message: string) { + Effect.runFork(bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })) } async function applyPlugin(load: PluginLoader.Loaded, input: PluginInput, hooks: Hooks[]) { @@ -161,24 +161,24 @@ export namespace Plugin { if (stage === "install") { const parsed = parsePluginSpecifier(spec) log.error("failed to install plugin", { pkg: parsed.pkg, version: parsed.version, error: message }) - publishPluginError(`Failed to install plugin ${parsed.pkg}@${parsed.version}: ${message}`) + publishPluginError(bus, `Failed to install plugin ${parsed.pkg}@${parsed.version}: ${message}`) return } if (stage === "compatibility") { log.warn("plugin incompatible", { path: spec, error: message }) - publishPluginError(`Plugin ${spec} skipped: ${message}`) + publishPluginError(bus, `Plugin ${spec} skipped: ${message}`) return } if (stage === "entry") { log.error("failed to resolve plugin server entry", { path: spec, error: message }) - publishPluginError(`Failed to load plugin ${spec}: ${message}`) + publishPluginError(bus, `Failed to load plugin ${spec}: ${message}`) return } log.error("failed to load plugin", { path: spec, target: resolved?.entry, error: message }) - publishPluginError(`Failed to load plugin ${spec}: ${message}`) + publishPluginError(bus, `Failed to load plugin ${spec}: ${message}`) }, }, }), diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 72089d8441ea..a97f3373d1fd 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -118,6 +118,8 @@ export namespace Pty { export const layer = Layer.effect( Service, Effect.gen(function* () { + const bus = yield* Bus.Service + const plugin = yield* Plugin.Service function teardown(session: Active) { try { session.process.kill() @@ -157,7 +159,7 @@ export namespace Pty { s.sessions.delete(id) log.info("removing session", { id }) teardown(session) - void Bus.publish(Event.Deleted, { id: session.info.id }) + yield* bus.publish(Event.Deleted, { id: session.info.id }) }) const list = Effect.fn("Pty.list")(function* () { @@ -172,95 +174,95 @@ export namespace Pty { const create = Effect.fn("Pty.create")(function* (input: CreateInput) { const s = yield* InstanceState.get(state) - return yield* Effect.promise(async () => { - const id = PtyID.ascending() - const command = input.command || Shell.preferred() - const args = input.args || [] - if (Shell.login(command)) { - args.push("-l") - } + const id = PtyID.ascending() + const command = input.command || Shell.preferred() + const args = input.args || [] + if (Shell.login(command)) { + args.push("-l") + } - const cwd = input.cwd || s.dir - const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} }) - const env = { - ...process.env, - ...input.env, - ...shellEnv.env, - TERM: "xterm-256color", - OPENCODE_TERMINAL: "1", - } as Record - - if (process.platform === "win32") { - env.LC_ALL = "C.UTF-8" - env.LC_CTYPE = "C.UTF-8" - env.LANG = "C.UTF-8" - } - log.info("creating session", { id, cmd: command, args, cwd }) + const cwd = input.cwd || s.dir + const shell = yield* plugin.trigger("shell.env", { cwd }, { env: {} }) + const env = { + ...process.env, + ...input.env, + ...shell.env, + TERM: "xterm-256color", + OPENCODE_TERMINAL: "1", + } as Record + + if (process.platform === "win32") { + env.LC_ALL = "C.UTF-8" + env.LC_CTYPE = "C.UTF-8" + env.LANG = "C.UTF-8" + } + log.info("creating session", { id, cmd: command, args, cwd }) - const spawn = await pty() - const proc = spawn(command, args, { + const spawn = yield* Effect.promise(() => pty()) + const proc = yield* Effect.sync(() => + spawn(command, args, { name: "xterm-256color", cwd, env, - }) - - const info = { - id, - title: input.title || `Terminal ${id.slice(-4)}`, - command, - args, - cwd, - status: "running", - pid: proc.pid, - } as const - const session: Active = { - info, - process: proc, - buffer: "", - bufferCursor: 0, - cursor: 0, - subscribers: new Map(), - } - s.sessions.set(id, session) - proc.onData( - Instance.bind((chunk) => { - session.cursor += chunk.length - - for (const [key, ws] of session.subscribers.entries()) { - if (ws.readyState !== 1) { - session.subscribers.delete(key) - continue - } - if (ws.data !== key) { - session.subscribers.delete(key) - continue - } - try { - ws.send(chunk) - } catch { - session.subscribers.delete(key) - } + }), + ) + + const info = { + id, + title: input.title || `Terminal ${id.slice(-4)}`, + command, + args, + cwd, + status: "running", + pid: proc.pid, + } as const + const session: Active = { + info, + process: proc, + buffer: "", + bufferCursor: 0, + cursor: 0, + subscribers: new Map(), + } + s.sessions.set(id, session) + proc.onData( + Instance.bind((chunk) => { + session.cursor += chunk.length + + for (const [key, ws] of session.subscribers.entries()) { + if (ws.readyState !== 1) { + session.subscribers.delete(key) + continue } + if (ws.data !== key) { + session.subscribers.delete(key) + continue + } + try { + ws.send(chunk) + } catch { + session.subscribers.delete(key) + } + } - session.buffer += chunk - if (session.buffer.length <= BUFFER_LIMIT) return - const excess = session.buffer.length - BUFFER_LIMIT - session.buffer = session.buffer.slice(excess) - session.bufferCursor += excess - }), - ) - proc.onExit( - Instance.bind(({ exitCode }) => { - if (session.info.status === "exited") return - log.info("session exited", { id, exitCode }) - session.info.status = "exited" - void Bus.publish(Event.Exited, { id, exitCode }) - Effect.runFork(remove(id)) - }), - ) - await Bus.publish(Event.Created, { info }) - return info - }) + session.buffer += chunk + if (session.buffer.length <= BUFFER_LIMIT) return + const excess = session.buffer.length - BUFFER_LIMIT + session.buffer = session.buffer.slice(excess) + session.bufferCursor += excess + }), + ) + proc.onExit( + Instance.bind(({ exitCode }) => { + if (session.info.status === "exited") return + log.info("session exited", { id, exitCode }) + session.info.status = "exited" + Effect.runFork(bus.publish(Event.Exited, { id, exitCode })) + Effect.runFork(remove(id)) + }), + ) + yield* bus.publish(Event.Created, { info }) + return info }) const update = Effect.fn("Pty.update")(function* (id: PtyID, input: UpdateInput) { @@ -273,7 +275,7 @@ export namespace Pty { if (input.size) { session.process.resize(input.size.cols, input.size.rows) } - void Bus.publish(Event.Updated, { info: session.info }) + yield* bus.publish(Event.Updated, { info: session.info }) return session.info }) @@ -361,7 +363,9 @@ export namespace Pty { }), ) - const { runPromise } = makeRuntime(Service, layer) + const defaultLayer = layer.pipe(Layer.provide(Bus.layer), Layer.provide(Plugin.defaultLayer)) + + const { runPromise } = makeRuntime(Service, defaultLayer) export async function list() { return runPromise((svc) => svc.list()) diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts index f46cdd1081f0..13470f0a0231 100644 --- a/packages/opencode/src/question/index.ts +++ b/packages/opencode/src/question/index.ts @@ -109,6 +109,7 @@ export namespace Question { export const layer = Layer.effect( Service, Effect.gen(function* () { + const bus = yield* Bus.Service const state = yield* InstanceState.make( Effect.fn("Question.state")(function* () { const state = { @@ -145,7 +146,7 @@ export namespace Question { tool: input.tool, } pending.set(id, { info, deferred }) - Bus.publish(Event.Asked, info) + yield* bus.publish(Event.Asked, info) return yield* Effect.ensuring( Deferred.await(deferred), @@ -164,7 +165,7 @@ export namespace Question { } pending.delete(input.requestID) log.info("replied", { requestID: input.requestID, answers: input.answers }) - Bus.publish(Event.Replied, { + yield* bus.publish(Event.Replied, { sessionID: existing.info.sessionID, requestID: existing.info.id, answers: input.answers, @@ -181,7 +182,7 @@ export namespace Question { } pending.delete(requestID) log.info("rejected", { requestID }) - Bus.publish(Event.Rejected, { + yield* bus.publish(Event.Rejected, { sessionID: existing.info.sessionID, requestID: existing.info.id, }) @@ -197,7 +198,9 @@ export namespace Question { }), ) - const { runPromise } = makeRuntime(Service, layer) + const defaultLayer = layer.pipe(Layer.provide(Bus.layer)) + + const { runPromise } = makeRuntime(Service, defaultLayer) export async function ask(input: { sessionID: SessionID diff --git a/packages/opencode/src/session/instruction.ts b/packages/opencode/src/session/instruction.ts index 02a536edd864..fc90093e99fe 100644 --- a/packages/opencode/src/session/instruction.ts +++ b/packages/opencode/src/session/instruction.ts @@ -248,18 +248,10 @@ export namespace Instruction { return runPromise((svc) => svc.systemPaths()) } - export async function system() { - return runPromise((svc) => svc.system()) - } - export function loaded(messages: MessageV2.WithParts[]) { return extract(messages) } - export async function find(dir: string) { - return runPromise((svc) => svc.find(dir)) - } - export async function resolve(messages: MessageV2.WithParts[], filepath: string, messageID: MessageID) { return runPromise((svc) => svc.resolve(messages, filepath, messageID)) } diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index b1a1b8dbd3d1..146c73f27712 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -512,7 +512,7 @@ export namespace SessionProcessor { Layer.provide(Snapshot.defaultLayer), Layer.provide(Agent.defaultLayer), Layer.provide(LLM.defaultLayer), - Layer.provide(Permission.layer), + Layer.provide(Permission.defaultLayer), Layer.provide(Plugin.defaultLayer), Layer.provide(SessionStatus.layer.pipe(Layer.provide(Bus.layer))), Layer.provide(Bus.layer), diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 5121f2452759..8facf7aba7ee 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1715,7 +1715,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the Layer.provide(SessionCompaction.defaultLayer), Layer.provide(SessionProcessor.defaultLayer), Layer.provide(Command.defaultLayer), - Layer.provide(Permission.layer), + Layer.provide(Permission.defaultLayer), Layer.provide(MCP.defaultLayer), Layer.provide(LSP.defaultLayer), Layer.provide(FileTime.defaultLayer), diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index d26a00d49339..f2b53f3baf58 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -174,8 +174,4 @@ export namespace SessionSummary { export async function diff(input: z.infer) { return runPromise((svc) => svc.diff(input)) } - - export async function computeDiff(input: { messages: MessageV2.WithParts[] }) { - return runPromise((svc) => svc.computeDiff(input)) - } } diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index aa62c027150a..7c952bc54db9 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -545,10 +545,6 @@ export namespace Snapshot { return runPromise((svc) => svc.init()) } - export async function cleanup() { - return runPromise((svc) => svc.cleanup()) - } - export async function track() { return runPromise((svc) => svc.track()) } diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 2620405144ac..1bb270716cb9 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -206,10 +206,6 @@ export namespace ToolRegistry { const { runPromise } = makeRuntime(Service, defaultLayer) - export async function register(tool: Tool.Info) { - return runPromise((svc) => svc.register(tool)) - } - export async function ids() { return runPromise((svc) => svc.ids()) } diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index f1d61babfaf5..0dac255b10f2 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -211,7 +211,7 @@ function liveRuntime(layer: Layer.Layer, provider = ProviderTest.fa Layer.provide(Session.defaultLayer), Layer.provide(Snapshot.defaultLayer), Layer.provide(layer), - Layer.provide(Permission.layer), + Layer.provide(Permission.defaultLayer), Layer.provide(Agent.defaultLayer), Layer.provide(Plugin.defaultLayer), Layer.provide(status), diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index a79e6967af44..0fc25c1a6b41 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -149,7 +149,7 @@ const deps = Layer.mergeAll( Session.defaultLayer, Snapshot.defaultLayer, AgentSvc.defaultLayer, - Permission.layer, + Permission.defaultLayer, Plugin.defaultLayer, Config.defaultLayer, LLM.defaultLayer, diff --git a/packages/opencode/test/session/prompt-effect.test.ts b/packages/opencode/test/session/prompt-effect.test.ts index d077f26d6b7b..6f81ffca39f7 100644 --- a/packages/opencode/test/session/prompt-effect.test.ts +++ b/packages/opencode/test/session/prompt-effect.test.ts @@ -150,7 +150,7 @@ function makeHttp() { LLM.defaultLayer, AgentSvc.defaultLayer, Command.defaultLayer, - Permission.layer, + Permission.defaultLayer, Plugin.defaultLayer, Config.defaultLayer, ProviderSvc.defaultLayer, diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 8e7f3c8c45dc..4f4376e341ca 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -114,7 +114,7 @@ function makeHttp() { LLM.defaultLayer, AgentSvc.defaultLayer, Command.defaultLayer, - Permission.layer, + Permission.defaultLayer, Plugin.defaultLayer, Config.defaultLayer, ProviderSvc.defaultLayer, From a76d88f57bf77ca7489150bdee8be4c3251f20aa Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 2 Apr 2026 19:16:40 -0500 Subject: [PATCH 045/616] fix: rm dynamic part from bash tool description again to restore cache hits across projects (#20771) --- packages/opencode/src/tool/bash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/tool/bash.txt b/packages/opencode/src/tool/bash.txt index 8d53c90ab4e8..668cea307ce4 100644 --- a/packages/opencode/src/tool/bash.txt +++ b/packages/opencode/src/tool/bash.txt @@ -2,7 +2,7 @@ Executes a given bash command in a persistent shell session with optional timeou Be aware: OS: ${os}, Shell: ${shell} -All commands run in ${directory} by default. Use the `workdir` parameter if you need to run a command in a different directory. AVOID using `cd && ` patterns - use `workdir` instead. +All commands run in the current working directory by default. Use the `workdir` parameter if you need to run a command in a different directory. AVOID using `cd && ` patterns - use `workdir` instead. IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead. From 0d0c37041b9050f1aae6b4f0826a23316b331f03 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 2 Apr 2026 20:56:56 -0400 Subject: [PATCH 046/616] refactor(share): effectify share next (#20596) --- packages/opencode/src/share/share-next.ts | 516 ++++++++++-------- .../opencode/test/share/share-next.test.ts | 397 +++++++++++--- 2 files changed, 626 insertions(+), 287 deletions(-) diff --git a/packages/opencode/src/share/share-next.ts b/packages/opencode/src/share/share-next.ts index 2a11094f8074..2eb9887ea4fe 100644 --- a/packages/opencode/src/share/share-next.ts +++ b/packages/opencode/src/share/share-next.ts @@ -1,152 +1,47 @@ -import { Bus } from "@/bus" +import type * as SDK from "@opencode-ai/sdk/v2" +import { Effect, Exit, Layer, Option, Schema, Scope, ServiceMap, Stream } from "effect" +import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { Account } from "@/account" -import { Config } from "@/config/config" +import { Bus } from "@/bus" +import { InstanceState } from "@/effect/instance-state" +import { makeRuntime } from "@/effect/run-service" import { Provider } from "@/provider/provider" -import { ProviderID, ModelID } from "@/provider/schema" +import { ModelID, ProviderID } from "@/provider/schema" import { Session } from "@/session" -import type { SessionID } from "@/session/schema" import { MessageV2 } from "@/session/message-v2" +import type { SessionID } from "@/session/schema" import { Database, eq } from "@/storage/db" -import { SessionShareTable } from "./share.sql" +import { Config } from "@/config/config" import { Log } from "@/util/log" -import type * as SDK from "@opencode-ai/sdk/v2" +import { SessionShareTable } from "./share.sql" export namespace ShareNext { const log = Log.create({ service: "share-next" }) + const disabled = process.env["OPENCODE_DISABLE_SHARE"] === "true" || process.env["OPENCODE_DISABLE_SHARE"] === "1" - type ApiEndpoints = { + export type Api = { create: string - sync: (shareId: string) => string - remove: (shareId: string) => string - data: (shareId: string) => string - } - - function apiEndpoints(resource: string): ApiEndpoints { - return { - create: `/api/${resource}`, - sync: (shareId) => `/api/${resource}/${shareId}/sync`, - remove: (shareId) => `/api/${resource}/${shareId}`, - data: (shareId) => `/api/${resource}/${shareId}/data`, - } - } - - const legacyApi = apiEndpoints("share") - const consoleApi = apiEndpoints("shares") - - export async function url() { - const req = await request() - return req.baseUrl + sync: (shareID: string) => string + remove: (shareID: string) => string + data: (shareID: string) => string } - export async function request(): Promise<{ + export type Req = { headers: Record - api: ApiEndpoints + api: Api baseUrl: string - }> { - const headers: Record = {} - - const active = await Account.active() - if (!active?.active_org_id) { - const baseUrl = await Config.get().then((x) => x.enterprise?.url ?? "https://opncd.ai") - return { headers, api: legacyApi, baseUrl } - } - - const token = await Account.token(active.id) - if (!token) { - throw new Error("No active account token available for sharing") - } - - headers["authorization"] = `Bearer ${token}` - headers["x-org-id"] = active.active_org_id - return { headers, api: consoleApi, baseUrl: active.url } - } - - const disabled = process.env["OPENCODE_DISABLE_SHARE"] === "true" || process.env["OPENCODE_DISABLE_SHARE"] === "1" - - export async function init() { - if (disabled) return - Bus.subscribe(Session.Event.Updated, async (evt) => { - const session = await Session.get(evt.properties.sessionID) - - await sync(session.id, [ - { - type: "session", - data: session, - }, - ]) - }) - Bus.subscribe(MessageV2.Event.Updated, async (evt) => { - const info = evt.properties.info - await sync(info.sessionID, [ - { - type: "message", - data: evt.properties.info, - }, - ]) - if (info.role === "user") { - await sync(info.sessionID, [ - { - type: "model", - data: [await Provider.getModel(info.model.providerID, info.model.modelID).then((m) => m)], - }, - ]) - } - }) - Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => { - await sync(evt.properties.part.sessionID, [ - { - type: "part", - data: evt.properties.part, - }, - ]) - }) - Bus.subscribe(Session.Event.Diff, async (evt) => { - await sync(evt.properties.sessionID, [ - { - type: "session_diff", - data: evt.properties.diff, - }, - ]) - }) } - export async function create(sessionID: SessionID) { - if (disabled) return { id: "", url: "", secret: "" } - log.info("creating share", { sessionID }) - const req = await request() - const response = await fetch(`${req.baseUrl}${req.api.create}`, { - method: "POST", - headers: { ...req.headers, "Content-Type": "application/json" }, - body: JSON.stringify({ sessionID: sessionID }), - }) - - if (!response.ok) { - const message = await response.text().catch(() => response.statusText) - throw new Error(`Failed to create share (${response.status}): ${message || response.statusText}`) - } - - const result = (await response.json()) as { id: string; url: string; secret: string } - - Database.use((db) => - db - .insert(SessionShareTable) - .values({ session_id: sessionID, id: result.id, secret: result.secret, url: result.url }) - .onConflictDoUpdate({ - target: SessionShareTable.session_id, - set: { id: result.id, secret: result.secret, url: result.url }, - }) - .run(), - ) - fullSync(sessionID) - return result - } + const ShareSchema = Schema.Struct({ + id: Schema.String, + url: Schema.String, + secret: Schema.String, + }) + export type Share = typeof ShareSchema.Type - function get(sessionID: SessionID) { - const row = Database.use((db) => - db.select().from(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).get(), - ) - if (!row) return - return { id: row.id, secret: row.secret, url: row.url } + type State = { + queue: Map }> + scope: Scope.Closeable } type Data = @@ -171,6 +66,31 @@ export namespace ShareNext { data: SDK.Model[] } + export interface Interface { + readonly init: () => Effect.Effect + readonly url: () => Effect.Effect + readonly request: () => Effect.Effect + readonly create: (sessionID: SessionID) => Effect.Effect + readonly remove: (sessionID: SessionID) => Effect.Effect + } + + export class Service extends ServiceMap.Service()("@opencode/ShareNext") {} + + const db = (fn: (d: Parameters[0] extends (trx: infer D) => any ? D : never) => T) => + Effect.sync(() => Database.use(fn)) + + function api(resource: string): Api { + return { + create: `/api/${resource}`, + sync: (shareID) => `/api/${resource}/${shareID}/sync`, + remove: (shareID) => `/api/${resource}/${shareID}`, + data: (shareID) => `/api/${resource}/${shareID}/data`, + } + } + + const legacyApi = api("share") + const consoleApi = api("shares") + function key(item: Data) { switch (item.type) { case "session": @@ -186,102 +106,264 @@ export namespace ShareNext { } } - const queue = new Map }>() - async function sync(sessionID: SessionID, data: Data[]) { - if (disabled) return - const existing = queue.get(sessionID) - if (existing) { - for (const item of data) { - existing.data.set(key(item), item) + export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const account = yield* Account.Service + const bus = yield* Bus.Service + const cfg = yield* Config.Service + const http = yield* HttpClient.HttpClient + const httpOk = HttpClient.filterStatusOk(http) + const provider = yield* Provider.Service + const session = yield* Session.Service + + function sync(sessionID: SessionID, data: Data[]): Effect.Effect { + return Effect.gen(function* () { + if (disabled) return + const s = yield* InstanceState.get(state) + const existing = s.queue.get(sessionID) + if (existing) { + for (const item of data) { + existing.data.set(key(item), item) + } + return + } + + const next = new Map(data.map((item) => [key(item), item])) + s.queue.set(sessionID, { data: next }) + yield* flush(sessionID).pipe( + Effect.delay(1000), + Effect.catchCause((cause) => + Effect.sync(() => { + log.error("share flush failed", { sessionID, cause }) + }), + ), + Effect.forkIn(s.scope), + ) + }) } - return - } - const dataMap = new Map() - for (const item of data) { - dataMap.set(key(item), item) - } + const state: InstanceState = yield* InstanceState.make( + Effect.fn("ShareNext.state")(function* (_ctx) { + const cache: State = { queue: new Map(), scope: yield* Scope.make() } + + yield* Effect.addFinalizer(() => + Scope.close(cache.scope, Exit.void).pipe( + Effect.andThen( + Effect.sync(() => { + cache.queue.clear() + }), + ), + ), + ) - const timeout = setTimeout(async () => { - const queued = queue.get(sessionID) - if (!queued) return - queue.delete(sessionID) - const share = get(sessionID) - if (!share) return - - const req = await request() - const response = await fetch(`${req.baseUrl}${req.api.sync(share.id)}`, { - method: "POST", - headers: { ...req.headers, "Content-Type": "application/json" }, - body: JSON.stringify({ - secret: share.secret, - data: Array.from(queued.data.values()), + if (disabled) return cache + + const watch = (def: D, fn: (evt: { properties: any }) => Effect.Effect) => + bus.subscribe(def as never).pipe( + Stream.runForEach((evt) => + fn(evt).pipe( + Effect.catchCause((cause) => + Effect.sync(() => { + log.error("share subscriber failed", { type: def.type, cause }) + }), + ), + ), + ), + Effect.forkScoped, + ) + + yield* watch(Session.Event.Updated, (evt) => + Effect.gen(function* () { + const info = yield* session.get(evt.properties.sessionID) + yield* sync(info.id, [{ type: "session", data: info }]) + }), + ) + yield* watch(MessageV2.Event.Updated, (evt) => + Effect.gen(function* () { + const info = evt.properties.info + yield* sync(info.sessionID, [{ type: "message", data: info }]) + if (info.role !== "user") return + const model = yield* provider.getModel(info.model.providerID, info.model.modelID) + yield* sync(info.sessionID, [{ type: "model", data: [model] }]) + }), + ) + yield* watch(MessageV2.Event.PartUpdated, (evt) => + sync(evt.properties.part.sessionID, [{ type: "part", data: evt.properties.part }]), + ) + yield* watch(Session.Event.Diff, (evt) => + sync(evt.properties.sessionID, [{ type: "session_diff", data: evt.properties.diff }]), + ) + + return cache }), + ) + + const request = Effect.fn("ShareNext.request")(function* () { + const headers: Record = {} + const active = yield* account.active() + if (Option.isNone(active) || !active.value.active_org_id) { + const baseUrl = (yield* cfg.get()).enterprise?.url ?? "https://opncd.ai" + return { headers, api: legacyApi, baseUrl } satisfies Req + } + + const token = yield* account.token(active.value.id) + if (Option.isNone(token)) { + throw new Error("No active account token available for sharing") + } + + headers.authorization = `Bearer ${token.value}` + headers["x-org-id"] = active.value.active_org_id + return { headers, api: consoleApi, baseUrl: active.value.url } satisfies Req }) - if (!response.ok) { - log.warn("failed to sync share", { sessionID, shareID: share.id, status: response.status }) - } - }, 1000) - queue.set(sessionID, { timeout, data: dataMap }) + const get = Effect.fnUntraced(function* (sessionID: SessionID) { + const row = yield* db((db) => + db.select().from(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).get(), + ) + if (!row) return + return { id: row.id, secret: row.secret, url: row.url } satisfies Share + }) + + const flush = Effect.fn("ShareNext.flush")(function* (sessionID: SessionID) { + if (disabled) return + const s = yield* InstanceState.get(state) + const queued = s.queue.get(sessionID) + if (!queued) return + + s.queue.delete(sessionID) + + const share = yield* get(sessionID) + if (!share) return + + const req = yield* request() + const res = yield* HttpClientRequest.post(`${req.baseUrl}${req.api.sync(share.id)}`).pipe( + HttpClientRequest.setHeaders(req.headers), + HttpClientRequest.bodyJson({ secret: share.secret, data: Array.from(queued.data.values()) }), + Effect.flatMap((r) => http.execute(r)), + ) + + if (res.status >= 400) { + log.warn("failed to sync share", { sessionID, shareID: share.id, status: res.status }) + } + }) + + const full = Effect.fn("ShareNext.full")(function* (sessionID: SessionID) { + log.info("full sync", { sessionID }) + const info = yield* session.get(sessionID) + const diffs = yield* session.diff(sessionID) + const messages = yield* Effect.sync(() => Array.from(MessageV2.stream(sessionID))) + const models = yield* Effect.forEach( + Array.from( + new Map( + messages + .filter((msg) => msg.info.role === "user") + .map((msg) => (msg.info as SDK.UserMessage).model) + .map((item) => [`${item.providerID}/${item.modelID}`, item] as const), + ).values(), + ), + (item) => provider.getModel(ProviderID.make(item.providerID), ModelID.make(item.modelID)), + { concurrency: 8 }, + ) + + yield* sync(sessionID, [ + { type: "session", data: info }, + ...messages.map((item) => ({ type: "message" as const, data: item.info })), + ...messages.flatMap((item) => item.parts.map((part) => ({ type: "part" as const, data: part }))), + { type: "session_diff", data: diffs }, + { type: "model", data: models }, + ]) + }) + + const init = Effect.fn("ShareNext.init")(function* () { + if (disabled) return + yield* InstanceState.get(state) + }) + + const url = Effect.fn("ShareNext.url")(function* () { + return (yield* request()).baseUrl + }) + + const create = Effect.fn("ShareNext.create")(function* (sessionID: SessionID) { + if (disabled) return { id: "", url: "", secret: "" } + log.info("creating share", { sessionID }) + const req = yield* request() + const result = yield* HttpClientRequest.post(`${req.baseUrl}${req.api.create}`).pipe( + HttpClientRequest.setHeaders(req.headers), + HttpClientRequest.bodyJson({ sessionID }), + Effect.flatMap((r) => httpOk.execute(r)), + Effect.flatMap(HttpClientResponse.schemaBodyJson(ShareSchema)), + ) + yield* db((db) => + db + .insert(SessionShareTable) + .values({ session_id: sessionID, id: result.id, secret: result.secret, url: result.url }) + .onConflictDoUpdate({ + target: SessionShareTable.session_id, + set: { id: result.id, secret: result.secret, url: result.url }, + }) + .run(), + ) + const s = yield* InstanceState.get(state) + yield* full(sessionID).pipe( + Effect.catchCause((cause) => + Effect.sync(() => { + log.error("share full sync failed", { sessionID, cause }) + }), + ), + Effect.forkIn(s.scope), + ) + return result + }) + + const remove = Effect.fn("ShareNext.remove")(function* (sessionID: SessionID) { + if (disabled) return + log.info("removing share", { sessionID }) + const share = yield* get(sessionID) + if (!share) return + + const req = yield* request() + yield* HttpClientRequest.delete(`${req.baseUrl}${req.api.remove(share.id)}`).pipe( + HttpClientRequest.setHeaders(req.headers), + HttpClientRequest.bodyJson({ secret: share.secret }), + Effect.flatMap((r) => httpOk.execute(r)), + ) + + yield* db((db) => db.delete(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).run()) + }) + + return Service.of({ init, url, request, create, remove }) + }), + ) + + export const defaultLayer = layer.pipe( + Layer.provide(Bus.layer), + Layer.provide(Account.defaultLayer), + Layer.provide(Config.defaultLayer), + Layer.provide(FetchHttpClient.layer), + Layer.provide(Provider.defaultLayer), + Layer.provide(Session.defaultLayer), + ) + + const { runPromise } = makeRuntime(Service, defaultLayer) + + export async function init() { + return runPromise((svc) => svc.init()) } - export async function remove(sessionID: SessionID) { - if (disabled) return - log.info("removing share", { sessionID }) - const share = get(sessionID) - if (!share) return - - const req = await request() - const response = await fetch(`${req.baseUrl}${req.api.remove(share.id)}`, { - method: "DELETE", - headers: { ...req.headers, "Content-Type": "application/json" }, - body: JSON.stringify({ - secret: share.secret, - }), - }) - - if (!response.ok) { - const message = await response.text().catch(() => response.statusText) - throw new Error(`Failed to remove share (${response.status}): ${message || response.statusText}`) - } + export async function url() { + return runPromise((svc) => svc.url()) + } - Database.use((db) => db.delete(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).run()) + export async function request(): Promise { + return runPromise((svc) => svc.request()) } - async function fullSync(sessionID: SessionID) { - log.info("full sync", { sessionID }) - const session = await Session.get(sessionID) - const diffs = await Session.diff(sessionID) - const messages = await Array.fromAsync(MessageV2.stream(sessionID)) - const models = await Promise.all( - Array.from( - new Map( - messages - .filter((m) => m.info.role === "user") - .map((m) => (m.info as SDK.UserMessage).model) - .map((m) => [`${m.providerID}/${m.modelID}`, m] as const), - ).values(), - ).map((m) => Provider.getModel(ProviderID.make(m.providerID), ModelID.make(m.modelID)).then((item) => item)), - ) - await sync(sessionID, [ - { - type: "session", - data: session, - }, - ...messages.map((x) => ({ - type: "message" as const, - data: x.info, - })), - ...messages.flatMap((x) => x.parts.map((y) => ({ type: "part" as const, data: y }))), - { - type: "session_diff", - data: diffs, - }, - { - type: "model", - data: models, - }, - ]) + export async function create(sessionID: SessionID) { + return runPromise((svc) => svc.create(sessionID)) + } + + export async function remove(sessionID: SessionID) { + return runPromise((svc) => svc.remove(sessionID)) } } diff --git a/packages/opencode/test/share/share-next.test.ts b/packages/opencode/test/share/share-next.test.ts index fc8d511509fe..12d71f19a01d 100644 --- a/packages/opencode/test/share/share-next.test.ts +++ b/packages/opencode/test/share/share-next.test.ts @@ -1,76 +1,333 @@ -import { test, expect, mock } from "bun:test" -import { ShareNext } from "../../src/share/share-next" -import { AccessToken, Account, AccountID, OrgID } from "../../src/account" +import { NodeFileSystem } from "@effect/platform-node" +import { beforeEach, describe, expect } from "bun:test" +import { Effect, Exit, Layer, Option } from "effect" +import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" + +import { AccessToken, AccountID, OrgID, RefreshToken } from "../../src/account" +import { Account } from "../../src/account" +import { AccountRepo } from "../../src/account/repo" +import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { Bus } from "../../src/bus" import { Config } from "../../src/config/config" +import { Provider } from "../../src/provider/provider" +import { Session } from "../../src/session" +import type { SessionID } from "../../src/session/schema" +import { ShareNext } from "../../src/share/share-next" +import { SessionShareTable } from "../../src/share/share.sql" +import { Database, eq } from "../../src/storage/db" +import { provideTmpdirInstance } from "../fixture/fixture" +import { resetDatabase } from "../fixture/db" +import { testEffect } from "../lib/effect" -test("ShareNext.request uses legacy share API without active org account", async () => { - const originalActive = Account.active - const originalConfigGet = Config.get - - Account.active = mock(async () => undefined) - Config.get = mock(async () => ({ enterprise: { url: "https://legacy-share.example.com" } })) - - try { - const req = await ShareNext.request() - - expect(req.api.create).toBe("/api/share") - expect(req.api.sync("shr_123")).toBe("/api/share/shr_123/sync") - expect(req.api.remove("shr_123")).toBe("/api/share/shr_123") - expect(req.api.data("shr_123")).toBe("/api/share/shr_123/data") - expect(req.baseUrl).toBe("https://legacy-share.example.com") - expect(req.headers).toEqual({}) - } finally { - Account.active = originalActive - Config.get = originalConfigGet - } -}) +const env = Layer.mergeAll( + Session.defaultLayer, + AccountRepo.layer, + NodeFileSystem.layer, + CrossSpawnSpawner.defaultLayer, +) +const it = testEffect(env) + +const json = (req: Parameters[0], body: unknown, status = 200) => + HttpClientResponse.fromWeb( + req, + new Response(JSON.stringify(body), { + status, + headers: { "content-type": "application/json" }, + }), + ) + +const none = HttpClient.make(() => Effect.die("unexpected http call")) + +function live(client: HttpClient.HttpClient) { + const http = Layer.succeed(HttpClient.HttpClient, client) + return ShareNext.layer.pipe( + Layer.provide(Bus.layer), + Layer.provide(Account.layer.pipe(Layer.provide(AccountRepo.layer), Layer.provide(http))), + Layer.provide(Config.defaultLayer), + Layer.provide(http), + Layer.provide(Provider.defaultLayer), + Layer.provide(Session.defaultLayer), + ) +} + +function wired(client: HttpClient.HttpClient) { + const http = Layer.succeed(HttpClient.HttpClient, client) + return Layer.mergeAll( + Bus.layer, + ShareNext.layer, + Session.layer, + AccountRepo.layer, + NodeFileSystem.layer, + CrossSpawnSpawner.defaultLayer, + ).pipe( + Layer.provide(Bus.layer), + Layer.provide(Account.layer.pipe(Layer.provide(AccountRepo.layer), Layer.provide(http))), + Layer.provide(Config.defaultLayer), + Layer.provide(http), + Layer.provide(Provider.defaultLayer), + ) +} + +const share = (id: SessionID) => + Database.use((db) => db.select().from(SessionShareTable).where(eq(SessionShareTable.session_id, id)).get()) -test("ShareNext.request uses org share API with auth headers when account is active", async () => { - const originalActive = Account.active - const originalToken = Account.token - - Account.active = mock(async () => ({ - id: AccountID.make("account-1"), - email: "user@example.com", - url: "https://control.example.com", - active_org_id: OrgID.make("org-1"), - })) - Account.token = mock(async () => AccessToken.make("st_test_token")) - - try { - const req = await ShareNext.request() - - expect(req.api.create).toBe("/api/shares") - expect(req.api.sync("shr_123")).toBe("/api/shares/shr_123/sync") - expect(req.api.remove("shr_123")).toBe("/api/shares/shr_123") - expect(req.api.data("shr_123")).toBe("/api/shares/shr_123/data") - expect(req.baseUrl).toBe("https://control.example.com") - expect(req.headers).toEqual({ - authorization: "Bearer st_test_token", - "x-org-id": "org-1", - }) - } finally { - Account.active = originalActive - Account.token = originalToken - } +const seed = (url: string, org?: string) => + AccountRepo.use((repo) => + repo.persistAccount({ + id: AccountID.make("account-1"), + email: "user@example.com", + url, + accessToken: AccessToken.make("st_test_token"), + refreshToken: RefreshToken.make("rt_test_token"), + expiry: Date.now() + 10 * 60_000, + orgID: org ? Option.some(OrgID.make(org)) : Option.none(), + }), + ) + +beforeEach(async () => { + await resetDatabase() }) -test("ShareNext.request fails when org account has no token", async () => { - const originalActive = Account.active - const originalToken = Account.token - - Account.active = mock(async () => ({ - id: AccountID.make("account-1"), - email: "user@example.com", - url: "https://control.example.com", - active_org_id: OrgID.make("org-1"), - })) - Account.token = mock(async () => undefined) - - try { - await expect(ShareNext.request()).rejects.toThrow("No active account token available for sharing") - } finally { - Account.active = originalActive - Account.token = originalToken - } +describe("ShareNext", () => { + it.live("request uses legacy share API without active org account", () => + provideTmpdirInstance( + () => + ShareNext.Service.use((svc) => + Effect.gen(function* () { + const req = yield* svc.request() + + expect(req.api.create).toBe("/api/share") + expect(req.api.sync("shr_123")).toBe("/api/share/shr_123/sync") + expect(req.api.remove("shr_123")).toBe("/api/share/shr_123") + expect(req.api.data("shr_123")).toBe("/api/share/shr_123/data") + expect(req.baseUrl).toBe("https://legacy-share.example.com") + expect(req.headers).toEqual({}) + }), + ).pipe(Effect.provide(live(none))), + { config: { enterprise: { url: "https://legacy-share.example.com" } } }, + ), + ) + + it.live("request uses default URL when no enterprise config", () => + provideTmpdirInstance(() => + ShareNext.Service.use((svc) => + Effect.gen(function* () { + const req = yield* svc.request() + + expect(req.baseUrl).toBe("https://opncd.ai") + expect(req.api.create).toBe("/api/share") + expect(req.headers).toEqual({}) + }), + ).pipe(Effect.provide(live(none))), + ), + ) + + it.live("request uses org share API with auth headers when account is active", () => + provideTmpdirInstance(() => + Effect.gen(function* () { + yield* seed("https://control.example.com", "org-1") + + const req = yield* ShareNext.Service.use((svc) => svc.request()).pipe(Effect.provide(live(none))) + + expect(req.api.create).toBe("/api/shares") + expect(req.api.sync("shr_123")).toBe("/api/shares/shr_123/sync") + expect(req.api.remove("shr_123")).toBe("/api/shares/shr_123") + expect(req.api.data("shr_123")).toBe("/api/shares/shr_123/data") + expect(req.baseUrl).toBe("https://control.example.com") + expect(req.headers).toEqual({ + authorization: "Bearer st_test_token", + "x-org-id": "org-1", + }) + }), + ), + ) + + it.live("create posts share, persists it, and returns the result", () => + provideTmpdirInstance( + () => + Effect.gen(function* () { + const session = yield* Session.Service.use((svc) => svc.create({ title: "test" })) + const seen: HttpClientRequest.HttpClientRequest[] = [] + const client = HttpClient.make((req) => { + seen.push(req) + if (req.url.endsWith("/api/share")) { + return Effect.succeed( + json(req, { + id: "shr_abc", + url: "https://legacy-share.example.com/share/abc", + secret: "sec_123", + }), + ) + } + return Effect.succeed(json(req, { ok: true })) + }) + + const result = yield* ShareNext.Service.use((svc) => svc.create(session.id)).pipe( + Effect.provide(live(client)), + ) + + expect(result.id).toBe("shr_abc") + expect(result.url).toBe("https://legacy-share.example.com/share/abc") + expect(result.secret).toBe("sec_123") + + const row = share(session.id) + expect(row?.id).toBe("shr_abc") + expect(row?.url).toBe("https://legacy-share.example.com/share/abc") + expect(row?.secret).toBe("sec_123") + + expect(seen).toHaveLength(1) + expect(seen[0].method).toBe("POST") + expect(seen[0].url).toBe("https://legacy-share.example.com/api/share") + }), + { config: { enterprise: { url: "https://legacy-share.example.com" } } }, + ), + ) + + it.live("remove deletes the persisted share and calls the delete endpoint", () => + provideTmpdirInstance( + () => + Effect.gen(function* () { + const session = yield* Session.Service.use((svc) => svc.create({ title: "test" })) + const seen: HttpClientRequest.HttpClientRequest[] = [] + const client = HttpClient.make((req) => { + seen.push(req) + if (req.method === "POST") { + return Effect.succeed( + json(req, { + id: "shr_abc", + url: "https://legacy-share.example.com/share/abc", + secret: "sec_123", + }), + ) + } + return Effect.succeed(HttpClientResponse.fromWeb(req, new Response(null, { status: 200 }))) + }) + + yield* Effect.gen(function* () { + yield* ShareNext.Service.use((svc) => svc.create(session.id)) + yield* ShareNext.Service.use((svc) => svc.remove(session.id)) + }).pipe(Effect.provide(live(client))) + + expect(share(session.id)).toBeUndefined() + expect(seen.map((req) => [req.method, req.url])).toEqual([ + ["POST", "https://legacy-share.example.com/api/share"], + ["DELETE", "https://legacy-share.example.com/api/share/shr_abc"], + ]) + }), + { config: { enterprise: { url: "https://legacy-share.example.com" } } }, + ), + ) + + it.live("create fails on a non-ok response and does not persist a share", () => + provideTmpdirInstance(() => + Effect.gen(function* () { + const session = yield* Session.Service.use((svc) => svc.create({ title: "test" })) + const client = HttpClient.make((req) => Effect.succeed(json(req, { error: "bad" }, 500))) + + const exit = yield* ShareNext.Service.use((svc) => Effect.exit(svc.create(session.id))).pipe( + Effect.provide(live(client)), + ) + + expect(Exit.isFailure(exit)).toBe(true) + expect(share(session.id)).toBeUndefined() + }), + ), + ) + + it.live("ShareNext coalesces rapid diff events into one delayed sync with latest data", () => + provideTmpdirInstance( + () => { + const seen: Array<{ url: string; body: string }> = [] + const client = HttpClient.make((req) => { + if (req.url.endsWith("/sync") && req.body._tag === "Uint8Array") { + seen.push({ url: req.url, body: new TextDecoder().decode(req.body.body) }) + } + return Effect.succeed(json(req, { ok: true })) + }) + + return Effect.gen(function* () { + const bus = yield* Bus.Service + const share = yield* ShareNext.Service + const session = yield* Session.Service + + const info = yield* session.create({ title: "first" }) + yield* share.init() + yield* Effect.sleep(50) + yield* Effect.sync(() => + Database.use((db) => + db + .insert(SessionShareTable) + .values({ + session_id: info.id, + id: "shr_abc", + url: "https://legacy-share.example.com/share/abc", + secret: "sec_123", + }) + .run(), + ), + ) + + yield* bus.publish(Session.Event.Diff, { + sessionID: info.id, + diff: [ + { + file: "a.ts", + before: "one", + after: "two", + additions: 1, + deletions: 1, + status: "modified", + }, + ], + }) + yield* bus.publish(Session.Event.Diff, { + sessionID: info.id, + diff: [ + { + file: "b.ts", + before: "old", + after: "new", + additions: 2, + deletions: 0, + status: "modified", + }, + ], + }) + yield* Effect.sleep(1_250) + + expect(seen).toHaveLength(1) + expect(seen[0].url).toBe("https://legacy-share.example.com/api/share/shr_abc/sync") + + const body = JSON.parse(seen[0].body) as { + secret: string + data: Array<{ + type: string + data: Array<{ + file: string + before: string + after: string + additions: number + deletions: number + status?: string + }> + }> + } + expect(body.secret).toBe("sec_123") + expect(body.data).toHaveLength(1) + expect(body.data[0].type).toBe("session_diff") + expect(body.data[0].data).toEqual([ + { + file: "b.ts", + before: "old", + after: "new", + additions: 2, + deletions: 0, + status: "modified", + }, + ]) + }).pipe(Effect.provide(wired(client))) + }, + { config: { enterprise: { url: "https://legacy-share.example.com" } } }, + ), + ) }) From d98a501aaf4f4286479ca6381d1f822e03296edc Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Fri, 3 Apr 2026 11:26:53 +1000 Subject: [PATCH 047/616] fix: call models.dev once instead of twice on start (#20765) --- packages/opencode/src/cli/cmd/models.ts | 2 +- packages/opencode/src/cli/cmd/providers.ts | 2 +- packages/opencode/src/provider/models.ts | 56 +++++++++++++++++----- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/packages/opencode/src/cli/cmd/models.ts b/packages/opencode/src/cli/cmd/models.ts index 3e3672926d08..4670aa5f2fee 100644 --- a/packages/opencode/src/cli/cmd/models.ts +++ b/packages/opencode/src/cli/cmd/models.ts @@ -28,7 +28,7 @@ export const ModelsCommand = cmd({ }, handler: async (args) => { if (args.refresh) { - await ModelsDev.refresh() + await ModelsDev.refresh(true) UI.println(UI.Style.TEXT_SUCCESS_BOLD + "Models cache refreshed" + UI.Style.TEXT_NORMAL) } diff --git a/packages/opencode/src/cli/cmd/providers.ts b/packages/opencode/src/cli/cmd/providers.ts index 581809e90eb4..1ab0ecc7bc71 100644 --- a/packages/opencode/src/cli/cmd/providers.ts +++ b/packages/opencode/src/cli/cmd/providers.ts @@ -303,7 +303,7 @@ export const ProvidersLoginCommand = cmd({ prompts.outro("Done") return } - await ModelsDev.refresh().catch(() => {}) + await ModelsDev.refresh(true).catch(() => {}) const config = await Config.get() diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index 30901ea74f0e..c6ab5d8365c1 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -6,6 +6,8 @@ import { Installation } from "../installation" import { Flag } from "../flag/flag" import { lazy } from "@/util/lazy" import { Filesystem } from "../util/filesystem" +import { Flock } from "@/util/flock" +import { Hash } from "@/util/hash" // Try to import bundled snapshot (generated at build time) // Falls back to undefined in dev mode when snapshot doesn't exist @@ -13,7 +15,12 @@ import { Filesystem } from "../util/filesystem" export namespace ModelsDev { const log = Log.create({ service: "models.dev" }) - const filepath = path.join(Global.Path.cache, "models.json") + const source = url() + const filepath = path.join( + Global.Path.cache, + source === "https://models.dev" ? "models.json" : `models-${Hash.fast(source)}.json`, + ) + const ttl = 5 * 60 * 1000 export const Model = z.object({ id: z.string(), @@ -85,6 +92,22 @@ export namespace ModelsDev { return Flag.OPENCODE_MODELS_URL || "https://models.dev" } + function fresh() { + return Date.now() - Number(Filesystem.stat(filepath)?.mtimeMs ?? 0) < ttl + } + + function skip(force: boolean) { + return !force && fresh() + } + + const fetchApi = async () => { + const result = await fetch(`${url()}/api.json`, { + headers: { "User-Agent": Installation.USER_AGENT }, + signal: AbortSignal.timeout(10000), + }) + return { ok: result.ok, text: await result.text() } + } + export const Data = lazy(async () => { const result = await Filesystem.readJson(Flag.OPENCODE_MODELS_PATH ?? filepath).catch(() => {}) if (result) return result @@ -94,8 +117,17 @@ export namespace ModelsDev { .catch(() => undefined) if (snapshot) return snapshot if (Flag.OPENCODE_DISABLE_MODELS_FETCH) return {} - const json = await fetch(`${url()}/api.json`).then((x) => x.text()) - return JSON.parse(json) + return Flock.withLock(`models-dev:${filepath}`, async () => { + const result = await Filesystem.readJson(Flag.OPENCODE_MODELS_PATH ?? filepath).catch(() => {}) + if (result) return result + const result2 = await fetchApi() + if (result2.ok) { + await Filesystem.write(filepath, result2.text).catch((e) => { + log.error("Failed to write models cache", { error: e }) + }) + } + return JSON.parse(result2.text) + }) }) export async function get() { @@ -103,21 +135,19 @@ export namespace ModelsDev { return result as Record } - export async function refresh() { - const result = await fetch(`${url()}/api.json`, { - headers: { - "User-Agent": Installation.USER_AGENT, - }, - signal: AbortSignal.timeout(10 * 1000), + export async function refresh(force = false) { + if (skip(force)) return ModelsDev.Data.reset() + await Flock.withLock(`models-dev:${filepath}`, async () => { + if (skip(force)) return ModelsDev.Data.reset() + const result = await fetchApi() + if (!result.ok) return + await Filesystem.write(filepath, result.text) + ModelsDev.Data.reset() }).catch((e) => { log.error("Failed to fetch models.dev", { error: e, }) }) - if (result && result.ok) { - await Filesystem.write(filepath, await result.text()) - ModelsDev.Data.reset() - } } } From 1f87c22ac23ed20ebd5de37f3a1bf0dda69b82b0 Mon Sep 17 00:00:00 2001 From: Juan Pablo Carranza Hurtado <52012198+jpcarranza94@users.noreply.github.com> Date: Thu, 2 Apr 2026 20:09:53 -0600 Subject: [PATCH 048/616] fix: prevent Tool.define() wrapper accumulation on object-defined tools (#16952) Co-authored-by: Claude Opus 4.6 --- packages/opencode/src/tool/tool.ts | 2 +- .../opencode/test/tool/tool-define.test.ts | 108 ++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 packages/opencode/test/tool/tool-define.test.ts diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index 98fa50f8c7ac..069c6557eb8b 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -55,7 +55,7 @@ export namespace Tool { return { id, init: async (initCtx) => { - const toolInfo = init instanceof Function ? await init(initCtx) : init + const toolInfo = init instanceof Function ? await init(initCtx) : { ...init } const execute = toolInfo.execute toolInfo.execute = async (args, ctx) => { try { diff --git a/packages/opencode/test/tool/tool-define.test.ts b/packages/opencode/test/tool/tool-define.test.ts new file mode 100644 index 000000000000..532510f6819e --- /dev/null +++ b/packages/opencode/test/tool/tool-define.test.ts @@ -0,0 +1,108 @@ +import { describe, test, expect } from "bun:test" +import z from "zod" +import { Tool } from "../../src/tool/tool" + +const params = z.object({ input: z.string() }) +const defaultArgs = { input: "test" } + +function makeTool(id: string, executeFn?: () => void) { + return { + description: "test tool", + parameters: params, + async execute() { + executeFn?.() + return { title: "test", output: "ok", metadata: {} } + }, + } +} + +describe("Tool.define", () => { + test("object-defined tool does not mutate the original init object", async () => { + const original = makeTool("test") + const originalExecute = original.execute + + const tool = Tool.define("test-tool", original) + + await tool.init() + await tool.init() + await tool.init() + + // The original object's execute should never be overwritten + expect(original.execute).toBe(originalExecute) + }) + + test("object-defined tool does not accumulate wrapper layers across init() calls", async () => { + let executeCalls = 0 + + const tool = Tool.define("test-tool", makeTool("test", () => executeCalls++)) + + // Call init() many times to simulate many agentic steps + for (let i = 0; i < 100; i++) { + await tool.init() + } + + // Resolve the tool and call execute + const resolved = await tool.init() + executeCalls = 0 + + // Capture the stack trace inside execute to measure wrapper depth + let stackInsideExecute = "" + const origExec = resolved.execute + resolved.execute = async (args: any, ctx: any) => { + const result = await origExec.call(resolved, args, ctx) + const err = new Error() + stackInsideExecute = err.stack || "" + return result + } + + await resolved.execute(defaultArgs, {} as any) + expect(executeCalls).toBe(1) + + // Count how many times tool.ts appears in the stack. + // With the fix: 1 wrapper layer (from the most recent init()). + // Without the fix: 101 wrapper layers from accumulated closures. + const toolTsFrames = stackInsideExecute.split("\n").filter((l) => l.includes("tool.ts")).length + expect(toolTsFrames).toBeLessThan(5) + }) + + test("function-defined tool returns fresh objects and is unaffected", async () => { + const tool = Tool.define("test-fn-tool", () => Promise.resolve(makeTool("test"))) + + const first = await tool.init() + const second = await tool.init() + + // Function-defined tools return distinct objects each time + expect(first).not.toBe(second) + }) + + test("object-defined tool returns distinct objects per init() call", async () => { + const tool = Tool.define("test-copy", makeTool("test")) + + const first = await tool.init() + const second = await tool.init() + + // Each init() should return a separate object so wrappers don't accumulate + expect(first).not.toBe(second) + }) + + test("validation still works after many init() calls", async () => { + const tool = Tool.define("test-validation", { + description: "validation test", + parameters: z.object({ count: z.number().int().positive() }), + async execute(args) { + return { title: "test", output: String(args.count), metadata: {} } + }, + }) + + for (let i = 0; i < 100; i++) { + await tool.init() + } + + const resolved = await tool.init() + + const result = await resolved.execute({ count: 42 }, {} as any) + expect(result.output).toBe("42") + + await expect(resolved.execute({ count: -1 }, {} as any)).rejects.toThrow("invalid arguments") + }) +}) From 22bd2cb7613c7775f8d8a3689e3893977d2e262f Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Fri, 3 Apr 2026 02:10:50 +0000 Subject: [PATCH 049/616] chore: generate --- packages/opencode/test/tool/tool-define.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/opencode/test/tool/tool-define.test.ts b/packages/opencode/test/tool/tool-define.test.ts index 532510f6819e..b3ed66c93542 100644 --- a/packages/opencode/test/tool/tool-define.test.ts +++ b/packages/opencode/test/tool/tool-define.test.ts @@ -34,7 +34,10 @@ describe("Tool.define", () => { test("object-defined tool does not accumulate wrapper layers across init() calls", async () => { let executeCalls = 0 - const tool = Tool.define("test-tool", makeTool("test", () => executeCalls++)) + const tool = Tool.define( + "test-tool", + makeTool("test", () => executeCalls++), + ) // Call init() many times to simulate many agentic steps for (let i = 0; i < 100; i++) { From d611d22018c79cad583bb2f668e013f1b5a4a995 Mon Sep 17 00:00:00 2001 From: Dax Date: Thu, 2 Apr 2026 22:34:33 -0400 Subject: [PATCH 050/616] add automatic heap snapshots for high-memory cli processes (#20788) --- packages/opencode/src/cli/cmd/tui/worker.ts | 3 ++ packages/opencode/src/cli/heap.ts | 59 +++++++++++++++++++++ packages/opencode/src/flag/flag.ts | 1 + packages/opencode/src/index.ts | 3 ++ 4 files changed, 66 insertions(+) create mode 100644 packages/opencode/src/cli/heap.ts diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index a83645d8927a..643676e3485d 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -13,6 +13,7 @@ import { Flag } from "@/flag/flag" import { setTimeout as sleep } from "node:timers/promises" import { writeHeapSnapshot } from "node:v8" import { WorkspaceID } from "@/control-plane/schema" +import { Heap } from "@/cli/heap" await Log.init({ print: process.argv.includes("--print-logs"), @@ -23,6 +24,8 @@ await Log.init({ })(), }) +Heap.start() + process.on("unhandledRejection", (e) => { Log.Default.error("rejection", { e: e instanceof Error ? e.message : e, diff --git a/packages/opencode/src/cli/heap.ts b/packages/opencode/src/cli/heap.ts new file mode 100644 index 000000000000..bb5a3d0937ad --- /dev/null +++ b/packages/opencode/src/cli/heap.ts @@ -0,0 +1,59 @@ +import path from "path" +import { writeHeapSnapshot } from "node:v8" +import { Flag } from "@/flag/flag" +import { Global } from "@/global" +import { Log } from "@/util/log" + +const log = Log.create({ service: "heap" }) +const MINUTE = 60_000 +const LIMIT = 2 * 1024 * 1024 * 1024 + +export namespace Heap { + let timer: Timer | undefined + let lock = false + let armed = true + + export function start() { + if (!Flag.OPENCODE_AUTO_HEAP_SNAPSHOT) return + if (timer) return + + const run = async () => { + if (lock) return + + const stat = process.memoryUsage() + if (stat.rss <= LIMIT) { + armed = true + return + } + if (!armed) return + + lock = true + armed = false + const file = path.join( + Global.Path.log, + `heap-${process.pid}-${new Date().toISOString().replace(/[:.]/g, "")}.heapsnapshot`, + ) + log.warn("heap usage exceeded limit", { + rss: stat.rss, + heap: stat.heapUsed, + file, + }) + + await Promise.resolve() + .then(() => writeHeapSnapshot(file)) + .catch((err) => { + log.error("failed to write heap snapshot", { + error: err instanceof Error ? err.message : String(err), + file, + }) + }) + + lock = false + } + + timer = setInterval(() => { + void run() + }, MINUTE) + timer.unref?.() + } +} diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 27190f2eb24e..1ac52dd17fa1 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -12,6 +12,7 @@ function falsy(key: string) { export namespace Flag { export const OPENCODE_AUTO_SHARE = truthy("OPENCODE_AUTO_SHARE") + export const OPENCODE_AUTO_HEAP_SNAPSHOT = truthy("OPENCODE_AUTO_HEAP_SNAPSHOT") export const OPENCODE_GIT_BASH_PATH = process.env["OPENCODE_GIT_BASH_PATH"] export const OPENCODE_CONFIG = process.env["OPENCODE_CONFIG"] export declare const OPENCODE_PURE: boolean diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index bb14e0588af8..1fa027abf904 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -35,6 +35,7 @@ import { JsonMigration } from "./storage/json-migration" import { Database } from "./storage/db" import { errorMessage } from "./util/error" import { PluginCommand } from "./cli/cmd/plug" +import { Heap } from "./cli/heap" process.on("unhandledRejection", (e) => { Log.Default.error("rejection", { @@ -96,6 +97,8 @@ const cli = yargs(args) })(), }) + Heap.start() + process.env.AGENT = "1" process.env.OPENCODE = "1" process.env.OPENCODE_PID = String(process.pid) From b032690dd8fa8efd4fd37f1e491ca078a91c74ef Mon Sep 17 00:00:00 2001 From: Kevin Flansburg Date: Thu, 2 Apr 2026 21:09:53 -0600 Subject: [PATCH 051/616] feat: Send x-session-affinity and x-parent-session-id headers (#20744) --- packages/opencode/src/session/llm.ts | 3 +++ packages/opencode/src/session/prompt.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index dc89db409e87..1813346cdc93 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -25,6 +25,7 @@ export namespace LLM { export type StreamInput = { user: MessageV2.User sessionID: string + parentSessionID?: string model: Provider.Model agent: Agent.Info permission?: Permission.Ruleset @@ -301,6 +302,8 @@ export namespace LLM { "x-opencode-client": Flag.OPENCODE_CLIENT, } : { + "x-session-affinity": input.sessionID, + ...(input.parentSessionID ? { "x-parent-session-id": input.parentSessionID } : {}), "User-Agent": `opencode/${Installation.VERSION}`, }), ...input.model.headers, diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 8facf7aba7ee..e4709ef47e03 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1512,6 +1512,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the agent, permission: session.permission, sessionID, + parentSessionID: session.parentID, system, messages: [...modelMsgs, ...(isLastStep ? [{ role: "assistant" as const, content: MAX_STEPS }] : [])], tools, From df65c7dd24203096da417bb8652d996947dc812b Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Fri, 3 Apr 2026 13:18:50 +1000 Subject: [PATCH 052/616] fix(sdk): handle Windows opencode spawn and shutdown (#20772) --- bun.lock | 10 +++++-- package.json | 2 ++ packages/opencode/package.json | 4 +-- packages/opencode/src/util/process.ts | 4 +++ packages/sdk/js/package.json | 9 ++++--- packages/sdk/js/src/process.ts | 31 +++++++++++++++++++++ packages/sdk/js/src/server.ts | 39 +++++++++++++++++---------- packages/sdk/js/src/v2/server.ts | 39 +++++++++++++++++---------- 8 files changed, 103 insertions(+), 35 deletions(-) create mode 100644 packages/sdk/js/src/process.ts diff --git a/bun.lock b/bun.lock index 5a9116748e31..f103c9a33392 100644 --- a/bun.lock +++ b/bun.lock @@ -354,7 +354,7 @@ "bun-pty": "0.4.8", "chokidar": "4.0.3", "clipboardy": "4.0.0", - "cross-spawn": "^7.0.6", + "cross-spawn": "catalog:", "decimal.js": "10.5.0", "diff": "catalog:", "drizzle-orm": "catalog:", @@ -409,7 +409,7 @@ "@tsconfig/bun": "catalog:", "@types/babel__core": "7.20.5", "@types/bun": "catalog:", - "@types/cross-spawn": "6.0.6", + "@types/cross-spawn": "catalog:", "@types/mime-types": "3.0.1", "@types/npmcli__arborist": "6.3.3", "@types/semver": "^7.5.8", @@ -495,9 +495,13 @@ "packages/sdk/js": { "name": "@opencode-ai/sdk", "version": "1.3.13", + "dependencies": { + "cross-spawn": "catalog:", + }, "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", + "@types/cross-spawn": "catalog:", "@types/node": "catalog:", "@typescript/native-preview": "catalog:", "typescript": "catalog:", @@ -666,11 +670,13 @@ "@tsconfig/bun": "1.0.9", "@tsconfig/node22": "22.0.2", "@types/bun": "1.3.11", + "@types/cross-spawn": "6.0.6", "@types/luxon": "3.7.1", "@types/node": "22.13.9", "@types/semver": "7.7.1", "@typescript/native-preview": "7.0.0-dev.20251207.1", "ai": "6.0.138", + "cross-spawn": "7.0.6", "diff": "8.0.2", "dompurify": "3.3.1", "drizzle-kit": "1.0.0-beta.19-d95b7a4", diff --git a/package.json b/package.json index cc2d3f4c21ed..fc73a94d264d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "catalog": { "@effect/platform-node": "4.0.0-beta.43", "@types/bun": "1.3.11", + "@types/cross-spawn": "6.0.6", "@octokit/rest": "22.0.0", "@hono/zod-validator": "0.4.2", "ulid": "3.0.1", @@ -47,6 +48,7 @@ "drizzle-orm": "1.0.0-beta.19-d95b7a4", "effect": "4.0.0-beta.43", "ai": "6.0.138", + "cross-spawn": "7.0.6", "hono": "4.10.7", "hono-openapi": "1.1.2", "fuzzysort": "3.1.0", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index eab2d23f214a..d4210574b77d 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -44,7 +44,7 @@ "@tsconfig/bun": "catalog:", "@types/babel__core": "7.20.5", "@types/bun": "catalog:", - "@types/cross-spawn": "6.0.6", + "@types/cross-spawn": "catalog:", "@types/mime-types": "3.0.1", "@types/npmcli__arborist": "6.3.3", "@types/semver": "^7.5.8", @@ -111,7 +111,7 @@ "bun-pty": "0.4.8", "chokidar": "4.0.3", "clipboardy": "4.0.0", - "cross-spawn": "^7.0.6", + "cross-spawn": "catalog:", "decimal.js": "10.5.0", "diff": "catalog:", "drizzle-orm": "catalog:", diff --git a/packages/opencode/src/util/process.ts b/packages/opencode/src/util/process.ts index 1230ed3236c4..e45ceb4710e4 100644 --- a/packages/opencode/src/util/process.ts +++ b/packages/opencode/src/util/process.ts @@ -144,7 +144,11 @@ export namespace Process { throw new RunFailedError(cmd, out.code, out.stdout, out.stderr) } + // Duplicated in `packages/sdk/js/src/process.ts` because the SDK cannot import + // `opencode` without creating a cycle. Keep both copies in sync. export async function stop(proc: ChildProcess) { + if (proc.exitCode !== null || proc.signalCode !== null) return + if (process.platform !== "win32" || !proc.pid) { proc.kill() return diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 231e8d9da645..ed4e47384844 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -23,9 +23,12 @@ "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", + "@types/cross-spawn": "catalog:", "@types/node": "catalog:", - "typescript": "catalog:", - "@typescript/native-preview": "catalog:" + "@typescript/native-preview": "catalog:", + "typescript": "catalog:" }, - "dependencies": {} + "dependencies": { + "cross-spawn": "catalog:" + } } diff --git a/packages/sdk/js/src/process.ts b/packages/sdk/js/src/process.ts new file mode 100644 index 000000000000..3111b424aa18 --- /dev/null +++ b/packages/sdk/js/src/process.ts @@ -0,0 +1,31 @@ +import { type ChildProcess, spawnSync } from "node:child_process" + +// Duplicated from `packages/opencode/src/util/process.ts` because the SDK cannot +// import `opencode` without creating a cycle (`opencode` depends on `@opencode-ai/sdk`). +export function stop(proc: ChildProcess) { + if (proc.exitCode !== null || proc.signalCode !== null) return + if (process.platform === "win32" && proc.pid) { + const out = spawnSync("taskkill", ["/pid", String(proc.pid), "/T", "/F"], { windowsHide: true }) + if (!out.error && out.status === 0) return + } + proc.kill() +} + +export function bindAbort(proc: ChildProcess, signal?: AbortSignal, onAbort?: () => void) { + if (!signal) return () => {} + const abort = () => { + clear() + stop(proc) + onAbort?.() + } + const clear = () => { + signal.removeEventListener("abort", abort) + proc.off("exit", clear) + proc.off("error", clear) + } + signal.addEventListener("abort", abort, { once: true }) + proc.on("exit", clear) + proc.on("error", clear) + if (signal.aborted) abort() + return clear +} diff --git a/packages/sdk/js/src/server.ts b/packages/sdk/js/src/server.ts index 174131ccfd5d..2d1ab29fc928 100644 --- a/packages/sdk/js/src/server.ts +++ b/packages/sdk/js/src/server.ts @@ -1,5 +1,6 @@ -import { spawn } from "node:child_process" +import launch from "cross-spawn" import { type Config } from "./gen/types.gen.js" +import { stop, bindAbort } from "./process.js" export type ServerOptions = { hostname?: string @@ -31,29 +32,38 @@ export async function createOpencodeServer(options?: ServerOptions) { const args = [`serve`, `--hostname=${options.hostname}`, `--port=${options.port}`] if (options.config?.logLevel) args.push(`--log-level=${options.config.logLevel}`) - const proc = spawn(`opencode`, args, { - signal: options.signal, + const proc = launch(`opencode`, args, { env: { ...process.env, OPENCODE_CONFIG_CONTENT: JSON.stringify(options.config ?? {}), }, }) + let clear = () => {} const url = await new Promise((resolve, reject) => { const id = setTimeout(() => { + clear() + stop(proc) reject(new Error(`Timeout waiting for server to start after ${options.timeout}ms`)) }, options.timeout) let output = "" + let resolved = false proc.stdout?.on("data", (chunk) => { + if (resolved) return output += chunk.toString() const lines = output.split("\n") for (const line of lines) { if (line.startsWith("opencode server listening")) { const match = line.match(/on\s+(https?:\/\/[^\s]+)/) if (!match) { - throw new Error(`Failed to parse server url from output: ${line}`) + clear() + stop(proc) + clearTimeout(id) + reject(new Error(`Failed to parse server url from output: ${line}`)) + return } clearTimeout(id) + resolved = true resolve(match[1]!) return } @@ -74,18 +84,17 @@ export async function createOpencodeServer(options?: ServerOptions) { clearTimeout(id) reject(error) }) - if (options.signal) { - options.signal.addEventListener("abort", () => { - clearTimeout(id) - reject(new Error("Aborted")) - }) - } + clear = bindAbort(proc, options.signal, () => { + clearTimeout(id) + reject(options.signal?.reason) + }) }) return { url, close() { - proc.kill() + clear() + stop(proc) }, } } @@ -106,8 +115,7 @@ export function createOpencodeTui(options?: TuiOptions) { args.push(`--agent=${options.agent}`) } - const proc = spawn(`opencode`, args, { - signal: options?.signal, + const proc = launch(`opencode`, args, { stdio: "inherit", env: { ...process.env, @@ -115,9 +123,12 @@ export function createOpencodeTui(options?: TuiOptions) { }, }) + const clear = bindAbort(proc, options?.signal) + return { close() { - proc.kill() + clear() + stop(proc) }, } } diff --git a/packages/sdk/js/src/v2/server.ts b/packages/sdk/js/src/v2/server.ts index 174131ccfd5d..48f1a253da8d 100644 --- a/packages/sdk/js/src/v2/server.ts +++ b/packages/sdk/js/src/v2/server.ts @@ -1,5 +1,6 @@ -import { spawn } from "node:child_process" +import launch from "cross-spawn" import { type Config } from "./gen/types.gen.js" +import { stop, bindAbort } from "../process.js" export type ServerOptions = { hostname?: string @@ -31,29 +32,38 @@ export async function createOpencodeServer(options?: ServerOptions) { const args = [`serve`, `--hostname=${options.hostname}`, `--port=${options.port}`] if (options.config?.logLevel) args.push(`--log-level=${options.config.logLevel}`) - const proc = spawn(`opencode`, args, { - signal: options.signal, + const proc = launch(`opencode`, args, { env: { ...process.env, OPENCODE_CONFIG_CONTENT: JSON.stringify(options.config ?? {}), }, }) + let clear = () => {} const url = await new Promise((resolve, reject) => { const id = setTimeout(() => { + clear() + stop(proc) reject(new Error(`Timeout waiting for server to start after ${options.timeout}ms`)) }, options.timeout) let output = "" + let resolved = false proc.stdout?.on("data", (chunk) => { + if (resolved) return output += chunk.toString() const lines = output.split("\n") for (const line of lines) { if (line.startsWith("opencode server listening")) { const match = line.match(/on\s+(https?:\/\/[^\s]+)/) if (!match) { - throw new Error(`Failed to parse server url from output: ${line}`) + clear() + stop(proc) + clearTimeout(id) + reject(new Error(`Failed to parse server url from output: ${line}`)) + return } clearTimeout(id) + resolved = true resolve(match[1]!) return } @@ -74,18 +84,17 @@ export async function createOpencodeServer(options?: ServerOptions) { clearTimeout(id) reject(error) }) - if (options.signal) { - options.signal.addEventListener("abort", () => { - clearTimeout(id) - reject(new Error("Aborted")) - }) - } + clear = bindAbort(proc, options.signal, () => { + clearTimeout(id) + reject(options.signal?.reason) + }) }) return { url, close() { - proc.kill() + clear() + stop(proc) }, } } @@ -106,8 +115,7 @@ export function createOpencodeTui(options?: TuiOptions) { args.push(`--agent=${options.agent}`) } - const proc = spawn(`opencode`, args, { - signal: options?.signal, + const proc = launch(`opencode`, args, { stdio: "inherit", env: { ...process.env, @@ -115,9 +123,12 @@ export function createOpencodeTui(options?: TuiOptions) { }, }) + const clear = bindAbort(proc, options?.signal) + return { close() { - proc.kill() + clear() + stop(proc) }, } } From 122cf4d5a7c7f903c0770abb57685753567d6dba Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Fri, 3 Apr 2026 03:53:46 +0000 Subject: [PATCH 053/616] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index 983c37b4e055..e1e8e12b7076 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-DEwIpQ55Bdgxh6Pk39IO1+h+NWUKHQHALevTHlC/MoQ=", - "aarch64-linux": "sha256-iJak0E3DIVuBbudPjgoqaGeikruhXbnFceUugmOy4j0=", - "aarch64-darwin": "sha256-WBb54Gp8EcsEuLa0iMkOkV9EtsoQa66sCtfMqKm4L7w=", - "x86_64-darwin": "sha256-zBNXSUu/37CV5FvxGpjZHjNH/E47H0kTIWg7v/L3Qzo=" + "x86_64-linux": "sha256-0jwPCu2Lod433GPQLHN8eEkhfpPviDFfkFJmuvkRdlE=", + "aarch64-linux": "sha256-Qi0IkGkaIBKZsPLTO8kaTbCVL0cEfVOm/Y/6VUVI9TY=", + "aarch64-darwin": "sha256-1eZBBLgYVkjg5RYN/etR1Mb5UjU3VelElBB5ug5hQdc=", + "x86_64-darwin": "sha256-jdXgA+kZb/foFHR40UiPif6rsA2GDVCCVHnJR3jBUGI=" } } From db75fae206cbf8689a0750e6766c6c8fbc941e5c Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Fri, 3 Apr 2026 20:41:30 +0800 Subject: [PATCH 054/616] electron: better menus (#20878) --- packages/desktop-electron/src/main/menu.ts | 35 +++++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/packages/desktop-electron/src/main/menu.ts b/packages/desktop-electron/src/main/menu.ts index d8997be310fe..12e2445bc3ce 100644 --- a/packages/desktop-electron/src/main/menu.ts +++ b/packages/desktop-electron/src/main/menu.ts @@ -1,4 +1,4 @@ -import { BrowserWindow, Menu, shell } from "electron" +import { Menu, shell } from "electron" import { UPDATER_ENABLED } from "./constants" import { createMainWindow } from "./windows" @@ -77,27 +77,46 @@ export function createMenu(deps: Deps) { { label: "Toggle Terminal", accelerator: "Ctrl+`", click: () => deps.trigger("terminal.toggle") }, { label: "Toggle File Tree", click: () => deps.trigger("fileTree.toggle") }, { type: "separator" }, - { label: "Back", click: () => deps.trigger("common.goBack") }, - { label: "Forward", click: () => deps.trigger("common.goForward") }, + { role: "reload" }, + { role: "toggleDevTools" }, + { type: "separator" }, + { role: "resetZoom" }, + { role: "zoomIn" }, + { role: "zoomOut" }, + { type: "separator" }, + { role: "togglefullscreen" }, + ], + }, + { + label: "Go", + submenu: [ + { label: "Back", accelerator: "Cmd+[", click: () => deps.trigger("common.goBack") }, + { label: "Forward", accelerator: "Cmd+]", click: () => deps.trigger("common.goForward") }, { type: "separator" }, { label: "Previous Session", - accelerator: "Option+ArrowUp", + accelerator: "Option+Up", click: () => deps.trigger("session.previous"), }, { label: "Next Session", - accelerator: "Option+ArrowDown", + accelerator: "Option+Down", click: () => deps.trigger("session.next"), }, { type: "separator" }, { - label: "Toggle Developer Tools", - accelerator: "Alt+Cmd+I", - click: () => BrowserWindow.getFocusedWindow()?.webContents.toggleDevTools(), + label: "Previous Project", + accelerator: "Cmd+Option+Up", + click: () => deps.trigger("project.previous"), + }, + { + label: "Next Project", + accelerator: "Cmd+Option+Down", + click: () => deps.trigger("project.next"), }, ], }, + { role: "windowMenu" }, { label: "Help", submenu: [ From e46bd97ce1f32188802b65e6264267f415e90e11 Mon Sep 17 00:00:00 2001 From: Nate Williams <50088025+natewill@users.noreply.github.com> Date: Fri, 3 Apr 2026 08:53:00 -0400 Subject: [PATCH 055/616] fix(core): fix restoring earlier messages in a reverted chain (#20780) --- packages/opencode/src/session/revert.ts | 1 + .../test/session/revert-compact.test.ts | 184 ++++++++++++++++++ 2 files changed, 185 insertions(+) diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index 50c3781e9c56..9df3f36eb8c7 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -72,6 +72,7 @@ export namespace SessionRevert { if (!rev) return session rev.snapshot = session.revert?.snapshot ?? (yield* snap.track()) + if (session.revert?.snapshot) yield* snap.restore(session.revert.snapshot) yield* snap.revert(patches) if (rev.snapshot) rev.diff = yield* snap.diff(rev.snapshot as string) const range = all.filter((msg) => msg.info.id >= rev!.messageID) diff --git a/packages/opencode/test/session/revert-compact.test.ts b/packages/opencode/test/session/revert-compact.test.ts index c7230772df36..95d90325ad36 100644 --- a/packages/opencode/test/session/revert-compact.test.ts +++ b/packages/opencode/test/session/revert-compact.test.ts @@ -1,10 +1,12 @@ import { describe, expect, test, beforeEach, afterEach } from "bun:test" +import fs from "fs/promises" import path from "path" import { Session } from "../../src/session" import { ModelID, ProviderID } from "../../src/provider/schema" import { SessionRevert } from "../../src/session/revert" import { SessionCompaction } from "../../src/session/compaction" import { MessageV2 } from "../../src/session/message-v2" +import { Snapshot } from "../../src/snapshot" import { Log } from "../../src/util/log" import { Instance } from "../../src/project/instance" import { MessageID, PartID } from "../../src/session/schema" @@ -70,6 +72,13 @@ function tool(sessionID: string, messageID: string) { }) } +const tokens = { + input: 0, + output: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, +} + describe("revert + compact workflow", () => { test("should properly handle compact command after revert", async () => { await using tmp = await tmpdir({ git: true }) @@ -434,4 +443,179 @@ describe("revert + compact workflow", () => { }, }) }) + + test("restore messages in sequential order", async () => { + await using tmp = await tmpdir({ git: true }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await fs.writeFile(path.join(tmp.path, "a.txt"), "a0") + await fs.writeFile(path.join(tmp.path, "b.txt"), "b0") + await fs.writeFile(path.join(tmp.path, "c.txt"), "c0") + + const session = await Session.create({}) + const sid = session.id + + const turn = async (file: string, next: string) => { + const u = await user(sid) + await text(sid, u.id, `${file}:${next}`) + const a = await assistant(sid, u.id, tmp.path) + const before = await Snapshot.track() + if (!before) throw new Error("expected snapshot") + await fs.writeFile(path.join(tmp.path, file), next) + const after = await Snapshot.track() + if (!after) throw new Error("expected snapshot") + const patch = await Snapshot.patch(before) + await Session.updatePart({ + id: PartID.ascending(), + messageID: a.id, + sessionID: sid, + type: "step-start", + snapshot: before, + }) + await Session.updatePart({ + id: PartID.ascending(), + messageID: a.id, + sessionID: sid, + type: "step-finish", + reason: "stop", + snapshot: after, + cost: 0, + tokens, + }) + await Session.updatePart({ + id: PartID.ascending(), + messageID: a.id, + sessionID: sid, + type: "patch", + hash: patch.hash, + files: patch.files, + }) + return u.id + } + + const first = await turn("a.txt", "a1") + const second = await turn("b.txt", "b2") + const third = await turn("c.txt", "c3") + + await SessionRevert.revert({ + sessionID: sid, + messageID: first, + }) + expect((await Session.get(sid)).revert?.messageID).toBe(first) + expect(await fs.readFile(path.join(tmp.path, "a.txt"), "utf-8")).toBe("a0") + expect(await fs.readFile(path.join(tmp.path, "b.txt"), "utf-8")).toBe("b0") + expect(await fs.readFile(path.join(tmp.path, "c.txt"), "utf-8")).toBe("c0") + + await SessionRevert.revert({ + sessionID: sid, + messageID: second, + }) + expect((await Session.get(sid)).revert?.messageID).toBe(second) + expect(await fs.readFile(path.join(tmp.path, "a.txt"), "utf-8")).toBe("a1") + expect(await fs.readFile(path.join(tmp.path, "b.txt"), "utf-8")).toBe("b0") + expect(await fs.readFile(path.join(tmp.path, "c.txt"), "utf-8")).toBe("c0") + + await SessionRevert.revert({ + sessionID: sid, + messageID: third, + }) + expect((await Session.get(sid)).revert?.messageID).toBe(third) + expect(await fs.readFile(path.join(tmp.path, "a.txt"), "utf-8")).toBe("a1") + expect(await fs.readFile(path.join(tmp.path, "b.txt"), "utf-8")).toBe("b2") + expect(await fs.readFile(path.join(tmp.path, "c.txt"), "utf-8")).toBe("c0") + + await SessionRevert.unrevert({ + sessionID: sid, + }) + expect((await Session.get(sid)).revert).toBeUndefined() + expect(await fs.readFile(path.join(tmp.path, "a.txt"), "utf-8")).toBe("a1") + expect(await fs.readFile(path.join(tmp.path, "b.txt"), "utf-8")).toBe("b2") + expect(await fs.readFile(path.join(tmp.path, "c.txt"), "utf-8")).toBe("c3") + }, + }) + }) + + test("restore same file in sequential order", async () => { + await using tmp = await tmpdir({ git: true }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await fs.writeFile(path.join(tmp.path, "a.txt"), "a0") + + const session = await Session.create({}) + const sid = session.id + + const turn = async (next: string) => { + const u = await user(sid) + await text(sid, u.id, `a.txt:${next}`) + const a = await assistant(sid, u.id, tmp.path) + const before = await Snapshot.track() + if (!before) throw new Error("expected snapshot") + await fs.writeFile(path.join(tmp.path, "a.txt"), next) + const after = await Snapshot.track() + if (!after) throw new Error("expected snapshot") + const patch = await Snapshot.patch(before) + await Session.updatePart({ + id: PartID.ascending(), + messageID: a.id, + sessionID: sid, + type: "step-start", + snapshot: before, + }) + await Session.updatePart({ + id: PartID.ascending(), + messageID: a.id, + sessionID: sid, + type: "step-finish", + reason: "stop", + snapshot: after, + cost: 0, + tokens, + }) + await Session.updatePart({ + id: PartID.ascending(), + messageID: a.id, + sessionID: sid, + type: "patch", + hash: patch.hash, + files: patch.files, + }) + return u.id + } + + const first = await turn("a1") + const second = await turn("a2") + const third = await turn("a3") + expect(await fs.readFile(path.join(tmp.path, "a.txt"), "utf-8")).toBe("a3") + + await SessionRevert.revert({ + sessionID: sid, + messageID: first, + }) + expect((await Session.get(sid)).revert?.messageID).toBe(first) + expect(await fs.readFile(path.join(tmp.path, "a.txt"), "utf-8")).toBe("a0") + + await SessionRevert.revert({ + sessionID: sid, + messageID: second, + }) + expect((await Session.get(sid)).revert?.messageID).toBe(second) + expect(await fs.readFile(path.join(tmp.path, "a.txt"), "utf-8")).toBe("a1") + + await SessionRevert.revert({ + sessionID: sid, + messageID: third, + }) + expect((await Session.get(sid)).revert?.messageID).toBe(third) + expect(await fs.readFile(path.join(tmp.path, "a.txt"), "utf-8")).toBe("a2") + + await SessionRevert.unrevert({ + sessionID: sid, + }) + expect((await Session.get(sid)).revert).toBeUndefined() + expect(await fs.readFile(path.join(tmp.path, "a.txt"), "utf-8")).toBe("a3") + }, + }) + }) }) From 12a710d8bd0ec1e553a7fbdf61f549246ffe744d Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Fri, 3 Apr 2026 18:42:01 +0530 Subject: [PATCH 056/616] fix(session): delay jump-to-bottom button (#20853) --- packages/app/src/pages/session.tsx | 11 ++++++++--- packages/app/src/pages/session/message-timeline.tsx | 7 +++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 98d06fda7439..ae895adbe9b4 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -347,6 +347,7 @@ export default function Page() { scroll: { overflow: false, bottom: true, + jump: false, }, }) @@ -1247,13 +1248,17 @@ export default function Page() { let scrollStateTarget: HTMLDivElement | undefined let fillFrame: number | undefined + const jumpThreshold = (el: HTMLDivElement) => Math.max(400, el.clientHeight) + const updateScrollState = (el: HTMLDivElement) => { const max = el.scrollHeight - el.clientHeight + const distance = max - el.scrollTop const overflow = max > 1 - const bottom = !overflow || el.scrollTop >= max - 2 + const bottom = !overflow || distance <= 2 + const jump = overflow && distance > jumpThreshold(el) - if (ui.scroll.overflow === overflow && ui.scroll.bottom === bottom) return - setUi("scroll", { overflow, bottom }) + if (ui.scroll.overflow === overflow && ui.scroll.bottom === bottom && ui.scroll.jump === jump) return + setUi("scroll", { overflow, bottom, jump }) } const scheduleScrollState = (el: HTMLDivElement) => { diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index a246abaf3ffc..e1d6da19587d 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -200,7 +200,7 @@ export function MessageTimeline(props: { mobileChanges: boolean mobileFallback: JSX.Element actions?: UserActions - scroll: { overflow: boolean; bottom: boolean } + scroll: { overflow: boolean; bottom: boolean; jump: boolean } onResumeScroll: () => void setScrollRef: (el: HTMLDivElement | undefined) => void onScheduleScrollState: (el: HTMLDivElement) => void @@ -568,10 +568,9 @@ export function MessageTimeline(props: {

-
- 0} - fallback={ + +
+ 0} + fallback={ + + + + } + > - + - } - > + +
+
- - - - - - {local.model.current()?.name ?? language.t("dialog.model.select.title")} - - - + (x === "default" ? language.t("common.default") : x)} - onSelect={(x) => local.model.variant.set(x === "default" ? undefined : x)} - class="capitalize max-w-[160px] text-text-base" - valueClass="truncate text-13-regular text-text-base" - triggerStyle={control()} - triggerProps={{ "data-action": "prompt-model-variant" }} - variant="ghost" - /> - -
+
+ Date: Fri, 3 Apr 2026 19:02:30 +0530 Subject: [PATCH 058/616] fix(session): disable todo dock auto-scroll (#20840) --- .../session/composer/session-todo-dock.tsx | 57 +------------------ 1 file changed, 3 insertions(+), 54 deletions(-) diff --git a/packages/app/src/pages/session/composer/session-todo-dock.tsx b/packages/app/src/pages/session/composer/session-todo-dock.tsx index 7928bcc9ca80..2214248b14f3 100644 --- a/packages/app/src/pages/session/composer/session-todo-dock.tsx +++ b/packages/app/src/pages/session/composer/session-todo-dock.tsx @@ -7,7 +7,7 @@ import { useSpring } from "@opencode-ai/ui/motion-spring" import { TextReveal } from "@opencode-ai/ui/text-reveal" import { TextStrikethrough } from "@opencode-ai/ui/text-strikethrough" import { createResizeObserver } from "@solid-primitives/resize-observer" -import { Index, createEffect, createMemo, on, onCleanup } from "solid-js" +import { Index, createEffect, createMemo, onCleanup } from "solid-js" import { createStore } from "solid-js/store" import { composerEnabled, composerProbe } from "@/testing/session-composer" import { useLanguage } from "@/context/language" @@ -210,76 +210,25 @@ export function SessionTodoDock(props: { opacity: `${Math.max(0, Math.min(1, 1 - hide()))}`, }} > - +
) } -function TodoList(props: { todos: Todo[]; open: boolean }) { +function TodoList(props: { todos: Todo[] }) { const [store, setStore] = createStore({ stuck: false, - scrolling: false, - }) - let scrollRef!: HTMLDivElement - let timer: number | undefined - - const inProgress = createMemo(() => props.todos.findIndex((todo) => todo.status === "in_progress")) - - const ensure = () => { - if (!props.open) return - if (store.scrolling) return - if (!scrollRef || scrollRef.offsetParent === null) return - - const el = scrollRef.querySelector("[data-in-progress]") - if (!(el instanceof HTMLElement)) return - - const topFade = 16 - const bottomFade = 44 - const container = scrollRef.getBoundingClientRect() - const rect = el.getBoundingClientRect() - const top = rect.top - container.top + scrollRef.scrollTop - const bottom = rect.bottom - container.top + scrollRef.scrollTop - const viewTop = scrollRef.scrollTop + topFade - const viewBottom = scrollRef.scrollTop + scrollRef.clientHeight - bottomFade - - if (top < viewTop) { - scrollRef.scrollTop = Math.max(0, top - topFade) - } else if (bottom > viewBottom) { - scrollRef.scrollTop = bottom - (scrollRef.clientHeight - bottomFade) - } - - setStore("stuck", scrollRef.scrollTop > 0) - } - - createEffect( - on([() => props.open, inProgress], () => { - if (!props.open || inProgress() < 0) return - requestAnimationFrame(ensure) - }), - ) - - onCleanup(() => { - if (!timer) return - window.clearTimeout(timer) }) return (
{ setStore("stuck", e.currentTarget.scrollTop > 0) - setStore("scrolling", true) - if (timer) window.clearTimeout(timer) - timer = window.setTimeout(() => { - setStore("scrolling", false) - if (inProgress() < 0) return - requestAnimationFrame(ensure) - }, 250) }} > From 5641b15bb62967ab87a8edb09e627fa54c89e481 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Fri, 3 Apr 2026 19:02:53 +0530 Subject: [PATCH 059/616] feat(ui): redesign modified files section in session turn (#20348) Co-authored-by: David Hill --- packages/ui/src/components/session-turn.css | 64 +++--- packages/ui/src/components/session-turn.tsx | 209 ++++++++++---------- packages/ui/src/i18n/en.ts | 4 + 3 files changed, 134 insertions(+), 143 deletions(-) diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css index c4a8726ac535..53e2b58f45a2 100644 --- a/packages/ui/src/components/session-turn.css +++ b/packages/ui/src/components/session-turn.css @@ -92,33 +92,15 @@ min-width: 0; } - [data-slot="session-turn-diffs"] - > [data-component="collapsible"] - > [data-slot="collapsible-trigger"][aria-expanded="true"] { - position: sticky; - top: var(--sticky-accordion-top, 0px); - z-index: 20; - height: 40px; - padding-bottom: 8px; - background-color: var(--background-stronger); - } - - [data-component="session-turn-diffs-trigger"] { - width: 100%; + [data-slot="session-turn-diffs-header"] { display: flex; - align-items: center; - justify-content: flex-start; - gap: 8px; - padding: 0; - } - - [data-slot="session-turn-diffs-title"] { - display: inline-flex; align-items: baseline; gap: 8px; + padding-bottom: 12px; } [data-slot="session-turn-diffs-label"] { + font-variant-numeric: tabular-nums; color: var(--text-strong); font-family: var(--font-family-sans); font-size: var(--font-size-base); @@ -126,28 +108,38 @@ line-height: var(--line-height-large); } - [data-slot="session-turn-diffs-count"] { - color: var(--text-base); + [data-slot="session-turn-diffs-toggle"] { + color: var(--text-interactive-base); font-family: var(--font-family-sans); - font-variant-numeric: tabular-nums; font-size: var(--font-size-base); font-weight: var(--font-weight-regular); - line-height: var(--line-height-x-large); + line-height: var(--line-height-large); + cursor: pointer; + opacity: 0; + transition: opacity 0.15s ease; + margin-left: 4px; } - [data-slot="session-turn-diffs-meta"] { - display: inline-flex; - align-items: center; - gap: 8px; - flex-shrink: 0; + [data-component="session-turn-diffs-group"]:hover [data-slot="session-turn-diffs-toggle"] { + opacity: 1; + } - [data-slot="collapsible-arrow"] { - margin-left: -6px; - transform: translateY(2px); - } + [data-component="session-turn-diffs-group"][data-show-all] [data-slot="session-turn-diffs-toggle"] { + opacity: 1; + } + + [data-slot="session-turn-diffs-more"] { + color: var(--text-weak); + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + line-height: var(--line-height-large); + margin-top: 12px; + padding: 0 0 6px; + cursor: pointer; + transition: color 0.15s ease; - [data-component="diff-changes"][data-variant="bars"] { - transform: translateY(1px); + &:hover { + color: var(--text-link-base); } } diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index fe029485a1bd..c20e5fb1ce53 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -12,7 +12,6 @@ import { AssistantParts, Message, MessageDivider, PART_MAPPING, type UserActions import { Card } from "./card" import { Accordion } from "./accordion" import { StickyAccordionHeader } from "./sticky-accordion-header" -import { Collapsible } from "./collapsible" import { DiffChanges } from "./diff-changes" import { Icon } from "./icon" import { TextShimmer } from "./text-shimmer" @@ -241,23 +240,20 @@ export function SessionTurn( }, []) .reverse() }) + const MAX_FILES = 10 const edited = createMemo(() => diffs().length) const [state, setState] = createStore({ - open: false, + showAll: false, expanded: [] as string[], }) - const open = () => state.open + const showAll = () => state.showAll const expanded = () => state.expanded - - createEffect( - on( - open, - (value, prev) => { - if (!value && prev) setState("expanded", []) - }, - { defer: true }, - ), - ) + const overflow = createMemo(() => Math.max(0, edited() - MAX_FILES)) + const visible = createMemo(() => (showAll() ? diffs() : diffs().slice(0, MAX_FILES))) + const toggleAll = () => { + autoScroll.pause() + setState("showAll", !showAll()) + } const assistantMessages = createMemo( () => { @@ -425,101 +421,100 @@ export function SessionTurn( 0 && !working()}> -
- setState("open", value)} variant="ghost"> - -
-
- {i18n.t("ui.sessionReview.change.modified")} - - {edited()} {i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")} - -
- - -
-
-
-
- - -
- - setState("expanded", Array.isArray(value) ? value : value ? [value] : []) - } - > - - {(diff) => { - const active = createMemo(() => expanded().includes(diff.file)) - const [visible, setVisible] = createSignal(false) - - createEffect( - on( - active, - (value) => { - if (!value) { - setVisible(false) - return - } - - requestAnimationFrame(() => { - if (!active()) return - setVisible(true) - }) - }, - { defer: true }, - ), - ) - - return ( - - - -
- - - - {`\u202A${getDirectory(diff.file)}\u202C`} - - - {getFilename(diff.file)} - -
- - - - - - -
-
-
-
- - -
- -
+
+
+ + {edited()} {i18n.t("ui.sessionTurn.diffs.changed")}{" "} + {i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")} + + + 0}> + + {showAll() ? i18n.t("ui.sessionTurn.diffs.showLess") : i18n.t("ui.sessionTurn.diffs.showAll")} + + +
+
+ setState("expanded", Array.isArray(value) ? value : value ? [value] : [])} + > + + {(diff) => { + const active = createMemo(() => expanded().includes(diff.file)) + const [shown, setShown] = createSignal(false) + + createEffect( + on( + active, + (value) => { + if (!value) { + setShown(false) + return + } + + requestAnimationFrame(() => { + if (!active()) return + setShown(true) + }) + }, + { defer: true }, + ), + ) + + return ( + + + +
+ + + + {`\u202A${getDirectory(diff.file)}\u202C`} + - - - ) - }} - - -
- - - + {getFilename(diff.file)} + +
+ + + + + + +
+
+ + + + +
+ +
+
+
+ + ) + }} + + + 0}> +
+ {i18n.t("ui.sessionTurn.diffs.more", { count: String(overflow()) })} +
+
+
diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index 18823aeaa19b..e66b55092b17 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -38,6 +38,10 @@ export const dict: Record = { "ui.sessionTurn.steps.hide": "Hide steps", "ui.sessionTurn.summary.response": "Response", "ui.sessionTurn.diff.showMore": "Show more changes ({{count}})", + "ui.sessionTurn.diffs.changed": "Changed", + "ui.sessionTurn.diffs.showAll": "Show all", + "ui.sessionTurn.diffs.showLess": "Show less", + "ui.sessionTurn.diffs.more": "+{{count}} more files", "ui.sessionTurn.retry.retrying": "retrying", "ui.sessionTurn.retry.inSeconds": "in {{seconds}}s", From 7a0806613fc70c0785e3a0e55eff2075d6a55fcd Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Fri, 3 Apr 2026 19:21:54 +0530 Subject: [PATCH 060/616] fix(app): hide default session timestamps (#20892) --- .../app/src/pages/layout/sidebar-items.tsx | 77 ++++++++++--------- .../src/pages/session/message-timeline.tsx | 15 ++-- packages/app/src/utils/session-title.ts | 7 ++ 3 files changed, 57 insertions(+), 42 deletions(-) create mode 100644 packages/app/src/utils/session-title.ts diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index 75dada05f08f..058bb5a0dbed 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -16,6 +16,7 @@ import { getAvatarColors, type LocalProject, useLayout } from "@/context/layout" import { useNotification } from "@/context/notification" import { usePermission } from "@/context/permission" import { messageAgentColor } from "@/utils/agent" +import { sessionTitle } from "@/utils/session-title" import { sessionPermissionRequest } from "../session/composer/session-request-tree" import { hasProjectPermissions } from "./helpers" @@ -101,42 +102,46 @@ const SessionRow = (props: { warmPress: () => void warmFocus: () => void cancelHoverPrefetch: () => void -}): JSX.Element => ( -
{ - props.setHoverSession(undefined) - if (props.sidebarOpened()) return - props.clearHoverProjectSoon() - }} - > -
{ + const title = () => sessionTitle(props.session.title) + + return ( + { + props.setHoverSession(undefined) + if (props.sidebarOpened()) return + props.clearHoverProjectSoon() + }} > - }> - - - - -
- - -
- - 0}> -
- - -
- {props.session.title} -
-) +
+ }> + + + + +
+ + +
+ + 0}> +
+ + +
+ {title()} + + ) +} const SessionHoverPreview = (props: { mobile?: boolean @@ -319,7 +324,7 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { fallback={ diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index e1d6da19587d..cbabbda72d8e 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -29,6 +29,7 @@ import { useSettings } from "@/context/settings" import { useSDK } from "@/context/sdk" import { useSync } from "@/context/sync" import { messageAgentColor } from "@/utils/agent" +import { sessionTitle } from "@/utils/session-title" import { parseCommentNote, readCommentMetadata } from "@/utils/comment-note" import { makeTimer } from "@solid-primitives/timer" @@ -43,7 +44,6 @@ type MessageComment = { const emptyMessages: MessageType[] = [] const idle = { type: "idle" as const } - type UserActions = { fork?: (input: { sessionID: string; messageID: string }) => Promise | void revert?: (input: { sessionID: string; messageID: string }) => Promise | void @@ -291,6 +291,7 @@ export function MessageTimeline(props: { return sync.session.get(id) }) const titleValue = createMemo(() => info()?.title) + const titleLabel = createMemo(() => sessionTitle(titleValue())) const shareUrl = createMemo(() => info()?.share?.url) const shareEnabled = createMemo(() => sync.data.config.share !== "disabled") const parentID = createMemo(() => info()?.parentID) @@ -399,7 +400,7 @@ export function MessageTimeline(props: { const openTitleEditor = () => { if (!sessionID()) return - setTitle({ editing: true, draft: titleValue() ?? "" }) + setTitle({ editing: true, draft: titleLabel() ?? "" }) requestAnimationFrame(() => { titleRef?.focus() titleRef?.select() @@ -417,7 +418,7 @@ export function MessageTimeline(props: { if (titleMutation.isPending) return const next = title.draft.trim() - if (!next || next === (titleValue() ?? "")) { + if (!next || next === (titleLabel() ?? "")) { setTitle("editing", false) return } @@ -532,7 +533,9 @@ export function MessageTimeline(props: { } function DialogDeleteSession(props: { sessionID: string }) { - const name = createMemo(() => sync.session.get(props.sessionID)?.title ?? language.t("command.session.new")) + const name = createMemo( + () => sessionTitle(sync.session.get(props.sessionID)?.title) ?? language.t("command.session.new"), + ) const handleDelete = async () => { await deleteSession(props.sessionID) dialog.close() @@ -673,7 +676,7 @@ export function MessageTimeline(props: {
- + - {titleValue()} + {titleLabel()} } > diff --git a/packages/app/src/utils/session-title.ts b/packages/app/src/utils/session-title.ts new file mode 100644 index 000000000000..ca04c010470c --- /dev/null +++ b/packages/app/src/utils/session-title.ts @@ -0,0 +1,7 @@ +const pattern = /^(New session|Child session) - \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/ + +export function sessionTitle(title?: string) { + if (!title) return title + const match = title.match(pattern) + return match?.[1] ?? title +} From 75cae735d46840a2700a273bc4c455f773b70b1a Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 3 Apr 2026 10:31:00 -0400 Subject: [PATCH 061/616] refactor(effect): resolve built tools through the registry (#20787) --- packages/opencode/src/cli/cmd/run.ts | 4 +- .../src/cli/cmd/tui/routes/session/index.tsx | 2 +- packages/opencode/src/question/index.ts | 2 +- packages/opencode/src/session/prompt.ts | 8 +- packages/opencode/src/tool/question.ts | 59 ++++++---- packages/opencode/src/tool/registry.ts | 86 ++++++++------ packages/opencode/src/tool/tool.ts | 92 +++++++++------ .../test/session/prompt-effect.test.ts | 4 +- .../test/session/snapshot-tool-race.test.ts | 4 +- packages/opencode/test/tool/question.test.ts | 108 ++++++++++-------- 10 files changed, 223 insertions(+), 146 deletions(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 0aeb864e8679..92b6156ca7af 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -28,13 +28,13 @@ import { BashTool } from "../../tool/bash" import { TodoWriteTool } from "../../tool/todo" import { Locale } from "../../util/locale" -type ToolProps = { +type ToolProps = { input: Tool.InferParameters metadata: Tool.InferMetadata part: ToolPart } -function props(part: ToolPart): ToolProps { +function props(part: ToolPart): ToolProps { const state = part.state return { input: state.input as Tool.InferParameters, diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 48d6f9cb8e66..afeed6a22f03 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -1572,7 +1572,7 @@ function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMess ) } -type ToolProps = { +type ToolProps = { input: Partial> metadata: Partial> permission: Record diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts index 13470f0a0231..615c699ce911 100644 --- a/packages/opencode/src/question/index.ts +++ b/packages/opencode/src/question/index.ts @@ -198,7 +198,7 @@ export namespace Question { }), ) - const defaultLayer = layer.pipe(Layer.provide(Bus.layer)) + export const defaultLayer = layer.pipe(Layer.provide(Bus.layer)) const { runPromise } = makeRuntime(Service, defaultLayer) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index e4709ef47e03..24996c8d4b29 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -560,7 +560,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the }) { const { task, model, lastUser, sessionID, session, msgs } = input const ctx = yield* InstanceState.context - const taskTool = yield* Effect.promise(() => TaskTool.init()) + const taskTool = yield* Effect.promise(() => registry.named.task.init()) const taskModel = task.model ? yield* getModel(task.model.providerID, task.model.modelID, sessionID) : model const assistantMessage: MessageV2.Assistant = yield* sessions.updateMessage({ id: MessageID.ascending(), @@ -583,7 +583,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the sessionID: assistantMessage.sessionID, type: "tool", callID: ulid(), - tool: TaskTool.id, + tool: registry.named.task.id, state: { status: "running", input: { @@ -1110,7 +1110,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the text: `Called the Read tool with the following input: ${JSON.stringify(args)}`, }, ] - const read = yield* Effect.promise(() => ReadTool.init()).pipe( + const read = yield* Effect.promise(() => registry.named.read.init()).pipe( Effect.flatMap((t) => provider.getModel(info.model.providerID, info.model.modelID).pipe( Effect.flatMap((mdl) => @@ -1174,7 +1174,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the if (part.mime === "application/x-directory") { const args = { filePath: filepath } - const result = yield* Effect.promise(() => ReadTool.init()).pipe( + const result = yield* Effect.promise(() => registry.named.read.init()).pipe( Effect.flatMap((t) => Effect.promise(() => t.execute(args, { diff --git a/packages/opencode/src/tool/question.ts b/packages/opencode/src/tool/question.ts index a2887546d4bc..dd99688880ea 100644 --- a/packages/opencode/src/tool/question.ts +++ b/packages/opencode/src/tool/question.ts @@ -1,33 +1,46 @@ import z from "zod" +import { Effect } from "effect" import { Tool } from "./tool" import { Question } from "../question" import DESCRIPTION from "./question.txt" -export const QuestionTool = Tool.define("question", { - description: DESCRIPTION, - parameters: z.object({ - questions: z.array(Question.Info.omit({ custom: true })).describe("Questions to ask"), - }), - async execute(params, ctx) { - const answers = await Question.ask({ - sessionID: ctx.sessionID, - questions: params.questions, - tool: ctx.callID ? { messageID: ctx.messageID, callID: ctx.callID } : undefined, - }) +const parameters = z.object({ + questions: z.array(Question.Info.omit({ custom: true })).describe("Questions to ask"), +}) - function format(answer: Question.Answer | undefined) { - if (!answer?.length) return "Unanswered" - return answer.join(", ") - } +type Metadata = { + answers: Question.Answer[] +} - const formatted = params.questions.map((q, i) => `"${q.question}"="${format(answers[i])}"`).join(", ") +export const QuestionTool = Tool.defineEffect( + "question", + Effect.gen(function* () { + const question = yield* Question.Service return { - title: `Asked ${params.questions.length} question${params.questions.length > 1 ? "s" : ""}`, - output: `User has answered your questions: ${formatted}. You can now continue with the user's answers in mind.`, - metadata: { - answers, + description: DESCRIPTION, + parameters, + async execute(params: z.infer, ctx: Tool.Context) { + const answers = await question + .ask({ + sessionID: ctx.sessionID, + questions: params.questions, + tool: ctx.callID ? { messageID: ctx.messageID, callID: ctx.callID } : undefined, + }) + .pipe(Effect.runPromise) + + const formatted = params.questions + .map((q, i) => `"${q.question}"="${answers[i]?.length ? answers[i].join(", ") : "Unanswered"}"`) + .join(", ") + + return { + title: `Asked ${params.questions.length} question${params.questions.length > 1 ? "s" : ""}`, + output: `User has answered your questions: ${formatted}. You can now continue with the user's answers in mind.`, + metadata: { + answers, + }, + } }, - } - }, -}) + } satisfies Tool.Def + }), +) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 1bb270716cb9..8e21c43e3cd9 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -33,6 +33,7 @@ import { Effect, Layer, ServiceMap } from "effect" import { InstanceState } from "@/effect/instance-state" import { makeRuntime } from "@/effect/run-service" import { Env } from "../env" +import { Question } from "../question" export namespace ToolRegistry { const log = Log.create({ service: "tool.registry" }) @@ -42,8 +43,11 @@ export namespace ToolRegistry { } export interface Interface { - readonly register: (tool: Tool.Info) => Effect.Effect readonly ids: () => Effect.Effect + readonly named: { + task: Tool.Info + read: Tool.Info + } readonly tools: ( model: { providerID: ProviderID; modelID: ModelID }, agent?: Agent.Info, @@ -52,12 +56,15 @@ export namespace ToolRegistry { export class Service extends ServiceMap.Service()("@opencode/ToolRegistry") {} - export const layer: Layer.Layer = Layer.effect( + export const layer: Layer.Layer = Layer.effect( Service, Effect.gen(function* () { const config = yield* Config.Service const plugin = yield* Plugin.Service + const build = (tool: T | Effect.Effect) => + Effect.isEffect(tool) ? tool : Effect.succeed(tool) + const state = yield* InstanceState.make( Effect.fn("ToolRegistry.state")(function* (ctx) { const custom: Tool.Info[] = [] @@ -112,43 +119,52 @@ export namespace ToolRegistry { }), ) + const invalid = yield* build(InvalidTool) + const ask = yield* build(QuestionTool) + const bash = yield* build(BashTool) + const read = yield* build(ReadTool) + const glob = yield* build(GlobTool) + const grep = yield* build(GrepTool) + const edit = yield* build(EditTool) + const write = yield* build(WriteTool) + const task = yield* build(TaskTool) + const fetch = yield* build(WebFetchTool) + const todo = yield* build(TodoWriteTool) + const search = yield* build(WebSearchTool) + const code = yield* build(CodeSearchTool) + const skill = yield* build(SkillTool) + const patch = yield* build(ApplyPatchTool) + const lsp = yield* build(LspTool) + const batch = yield* build(BatchTool) + const plan = yield* build(PlanExitTool) + const all = Effect.fn("ToolRegistry.all")(function* (custom: Tool.Info[]) { const cfg = yield* config.get() const question = ["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || Flag.OPENCODE_ENABLE_QUESTION_TOOL return [ - InvalidTool, - ...(question ? [QuestionTool] : []), - BashTool, - ReadTool, - GlobTool, - GrepTool, - EditTool, - WriteTool, - TaskTool, - WebFetchTool, - TodoWriteTool, - WebSearchTool, - CodeSearchTool, - SkillTool, - ApplyPatchTool, - ...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [LspTool] : []), - ...(cfg.experimental?.batch_tool === true ? [BatchTool] : []), - ...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [PlanExitTool] : []), + invalid, + ...(question ? [ask] : []), + bash, + read, + glob, + grep, + edit, + write, + task, + fetch, + todo, + search, + code, + skill, + patch, + ...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [lsp] : []), + ...(cfg.experimental?.batch_tool === true ? [batch] : []), + ...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [plan] : []), ...custom, ] }) - const register = Effect.fn("ToolRegistry.register")(function* (tool: Tool.Info) { - const s = yield* InstanceState.get(state) - const idx = s.custom.findIndex((t) => t.id === tool.id) - if (idx >= 0) { - s.custom.splice(idx, 1, tool) - return - } - s.custom.push(tool) - }) - const ids = Effect.fn("ToolRegistry.ids")(function* () { const s = yield* InstanceState.get(state) const tools = yield* all(s.custom) @@ -196,12 +212,18 @@ export namespace ToolRegistry { ) }) - return Service.of({ register, ids, tools }) + return Service.of({ ids, named: { task, read }, tools }) }), ) export const defaultLayer = Layer.unwrap( - Effect.sync(() => layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(Plugin.defaultLayer))), + Effect.sync(() => + layer.pipe( + Layer.provide(Config.defaultLayer), + Layer.provide(Plugin.defaultLayer), + Layer.provide(Question.defaultLayer), + ), + ), ) const { runPromise } = makeRuntime(Service, defaultLayer) diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index 069c6557eb8b..a107dad7e8b5 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -1,4 +1,5 @@ import z from "zod" +import { Effect } from "effect" import type { MessageV2 } from "../session/message-v2" import type { Agent } from "../agent/agent" import type { Permission } from "../permission" @@ -45,48 +46,67 @@ export namespace Tool { init: (ctx?: InitContext) => Promise> } - export type InferParameters = T extends Info ? z.infer

: never - export type InferMetadata = T extends Info ? M : never + export type InferParameters = + T extends Info + ? z.infer

+ : T extends Effect.Effect, any, any> + ? z.infer

+ : never + export type InferMetadata = + T extends Info ? M : T extends Effect.Effect, any, any> ? M : never + + function wrap( + id: string, + init: ((ctx?: InitContext) => Promise>) | Def, + ) { + return async (initCtx?: InitContext) => { + const toolInfo = init instanceof Function ? await init(initCtx) : { ...init } + const execute = toolInfo.execute + toolInfo.execute = async (args, ctx) => { + try { + toolInfo.parameters.parse(args) + } catch (error) { + if (error instanceof z.ZodError && toolInfo.formatValidationError) { + throw new Error(toolInfo.formatValidationError(error), { cause: error }) + } + throw new Error( + `The ${id} tool was called with invalid arguments: ${error}.\nPlease rewrite the input so it satisfies the expected schema.`, + { cause: error }, + ) + } + const result = await execute(args, ctx) + if (result.metadata.truncated !== undefined) { + return result + } + const truncated = await Truncate.output(result.output, {}, initCtx?.agent) + return { + ...result, + output: truncated.content, + metadata: { + ...result.metadata, + truncated: truncated.truncated, + ...(truncated.truncated && { outputPath: truncated.outputPath }), + }, + } + } + return toolInfo + } + } export function define( id: string, - init: Info["init"] | Def, + init: ((ctx?: InitContext) => Promise>) | Def, ): Info { return { id, - init: async (initCtx) => { - const toolInfo = init instanceof Function ? await init(initCtx) : { ...init } - const execute = toolInfo.execute - toolInfo.execute = async (args, ctx) => { - try { - toolInfo.parameters.parse(args) - } catch (error) { - if (error instanceof z.ZodError && toolInfo.formatValidationError) { - throw new Error(toolInfo.formatValidationError(error), { cause: error }) - } - throw new Error( - `The ${id} tool was called with invalid arguments: ${error}.\nPlease rewrite the input so it satisfies the expected schema.`, - { cause: error }, - ) - } - const result = await execute(args, ctx) - // skip truncation for tools that handle it themselves - if (result.metadata.truncated !== undefined) { - return result - } - const truncated = await Truncate.output(result.output, {}, initCtx?.agent) - return { - ...result, - output: truncated.content, - metadata: { - ...result.metadata, - truncated: truncated.truncated, - ...(truncated.truncated && { outputPath: truncated.outputPath }), - }, - } - } - return toolInfo - }, + init: wrap(id, init), } } + + export function defineEffect( + id: string, + init: Effect.Effect<((ctx?: InitContext) => Promise>) | Def, never, R>, + ): Effect.Effect, never, R> { + return Effect.map(init, (next) => ({ id, init: wrap(id, next) })) + } } diff --git a/packages/opencode/test/session/prompt-effect.test.ts b/packages/opencode/test/session/prompt-effect.test.ts index 6f81ffca39f7..a6fd1f27dbe3 100644 --- a/packages/opencode/test/session/prompt-effect.test.ts +++ b/packages/opencode/test/session/prompt-effect.test.ts @@ -15,6 +15,7 @@ import { Plugin } from "../../src/plugin" import { Provider as ProviderSvc } from "../../src/provider/provider" import type { Provider } from "../../src/provider/provider" import { ModelID, ProviderID } from "../../src/provider/schema" +import { Question } from "../../src/question" import { Session } from "../../src/session" import { LLM } from "../../src/session/llm" import { MessageV2 } from "../../src/session/message-v2" @@ -160,7 +161,8 @@ function makeHttp() { AppFileSystem.defaultLayer, status, ).pipe(Layer.provideMerge(infra)) - const registry = ToolRegistry.layer.pipe(Layer.provideMerge(deps)) + const question = Question.layer.pipe(Layer.provideMerge(deps)) + const registry = ToolRegistry.layer.pipe(Layer.provideMerge(question), Layer.provideMerge(deps)) const trunc = Truncate.layer.pipe(Layer.provideMerge(deps)) const proc = SessionProcessor.layer.pipe(Layer.provideMerge(deps)) const compact = SessionCompaction.layer.pipe(Layer.provideMerge(proc), Layer.provideMerge(deps)) diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 4f4376e341ca..019cf1a796dd 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -38,6 +38,7 @@ import { MCP } from "../../src/mcp" import { Permission } from "../../src/permission" import { Plugin } from "../../src/plugin" import { Provider as ProviderSvc } from "../../src/provider/provider" +import { Question } from "../../src/question" import { SessionCompaction } from "../../src/session/compaction" import { Instruction } from "../../src/session/instruction" import { SessionProcessor } from "../../src/session/processor" @@ -124,7 +125,8 @@ function makeHttp() { AppFileSystem.defaultLayer, status, ).pipe(Layer.provideMerge(infra)) - const registry = ToolRegistry.layer.pipe(Layer.provideMerge(deps)) + const question = Question.layer.pipe(Layer.provideMerge(deps)) + const registry = ToolRegistry.layer.pipe(Layer.provideMerge(question), Layer.provideMerge(deps)) const trunc = Truncate.layer.pipe(Layer.provideMerge(deps)) const proc = SessionProcessor.layer.pipe(Layer.provideMerge(deps)) const compact = SessionCompaction.layer.pipe(Layer.provideMerge(proc), Layer.provideMerge(deps)) diff --git a/packages/opencode/test/tool/question.test.ts b/packages/opencode/test/tool/question.test.ts index 9157aaa9a481..f1d9492ca8c4 100644 --- a/packages/opencode/test/tool/question.test.ts +++ b/packages/opencode/test/tool/question.test.ts @@ -1,8 +1,12 @@ -import { describe, expect, test, spyOn, beforeEach, afterEach } from "bun:test" -import { z } from "zod" +import { describe, expect } from "bun:test" +import { Effect, Fiber, Layer } from "effect" +import { Tool } from "../../src/tool/tool" import { QuestionTool } from "../../src/tool/question" -import * as QuestionModule from "../../src/question" +import { Question } from "../../src/question" import { SessionID, MessageID } from "../../src/session/schema" +import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { provideTmpdirInstance } from "../fixture/fixture" +import { testEffect } from "../lib/effect" const ctx = { sessionID: SessionID.make("ses_test-session"), @@ -15,55 +19,69 @@ const ctx = { ask: async () => {}, } -describe("tool.question", () => { - let askSpy: any - - beforeEach(() => { - askSpy = spyOn(QuestionModule.Question, "ask").mockImplementation(async () => { - return [] - }) - }) +const it = testEffect(Layer.mergeAll(Question.defaultLayer, CrossSpawnSpawner.defaultLayer)) - afterEach(() => { - askSpy.mockRestore() - }) +const pending = Effect.fn("QuestionToolTest.pending")(function* (question: Question.Interface) { + for (;;) { + const items = yield* question.list() + const item = items[0] + if (item) return item + yield* Effect.sleep("10 millis") + } +}) - test("should successfully execute with valid question parameters", async () => { - const tool = await QuestionTool.init() - const questions = [ - { - question: "What is your favorite color?", - header: "Color", - options: [ - { label: "Red", description: "The color of passion" }, - { label: "Blue", description: "The color of sky" }, - ], - multiple: false, - }, - ] +describe("tool.question", () => { + it.live("should successfully execute with valid question parameters", () => + provideTmpdirInstance(() => + Effect.gen(function* () { + const question = yield* Question.Service + const toolInfo = yield* QuestionTool + const tool = yield* Effect.promise(() => toolInfo.init()) + const questions = [ + { + question: "What is your favorite color?", + header: "Color", + options: [ + { label: "Red", description: "The color of passion" }, + { label: "Blue", description: "The color of sky" }, + ], + multiple: false, + }, + ] - askSpy.mockResolvedValueOnce([["Red"]]) + const fiber = yield* Effect.promise(() => tool.execute({ questions }, ctx)).pipe(Effect.forkScoped) + const item = yield* pending(question) + yield* question.reply({ requestID: item.id, answers: [["Red"]] }) - const result = await tool.execute({ questions }, ctx) - expect(askSpy).toHaveBeenCalledTimes(1) - expect(result.title).toBe("Asked 1 question") - }) + const result = yield* Fiber.join(fiber) + expect(result.title).toBe("Asked 1 question") + }), + ), + ) - test("should now pass with a header longer than 12 but less than 30 chars", async () => { - const tool = await QuestionTool.init() - const questions = [ - { - question: "What is your favorite animal?", - header: "This Header is Over 12", - options: [{ label: "Dog", description: "Man's best friend" }], - }, - ] + it.live("should now pass with a header longer than 12 but less than 30 chars", () => + provideTmpdirInstance(() => + Effect.gen(function* () { + const question = yield* Question.Service + const toolInfo = yield* QuestionTool + const tool = yield* Effect.promise(() => toolInfo.init()) + const questions = [ + { + question: "What is your favorite animal?", + header: "This Header is Over 12", + options: [{ label: "Dog", description: "Man's best friend" }], + }, + ] - askSpy.mockResolvedValueOnce([["Dog"]]) + const fiber = yield* Effect.promise(() => tool.execute({ questions }, ctx)).pipe(Effect.forkScoped) + const item = yield* pending(question) + yield* question.reply({ requestID: item.id, answers: [["Dog"]] }) - const result = await tool.execute({ questions }, ctx) - expect(result.output).toContain(`"What is your favorite animal?"="Dog"`) - }) + const result = yield* Fiber.join(fiber) + expect(result.output).toContain(`"What is your favorite animal?"="Dog"`) + }), + ), + ) // intentionally removed the zod validation due to tool call errors, hoping prompting is gonna be good enough // test("should throw an Error for header exceeding 30 characters", async () => { From 9f0aedfa21c41461823bace4bbbe7c8ad35726fa Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Fri, 3 Apr 2026 20:06:33 +0530 Subject: [PATCH 062/616] fix: restore prompt focus after footer selection (#20841) --- .../e2e/prompt/prompt-footer-focus.spec.ts | 88 +++++++++++++++++++ .../src/components/dialog-select-model.tsx | 35 ++++---- packages/app/src/components/prompt-input.tsx | 20 ++++- 3 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 packages/app/e2e/prompt/prompt-footer-focus.spec.ts diff --git a/packages/app/e2e/prompt/prompt-footer-focus.spec.ts b/packages/app/e2e/prompt/prompt-footer-focus.spec.ts new file mode 100644 index 000000000000..4609f4b3d943 --- /dev/null +++ b/packages/app/e2e/prompt/prompt-footer-focus.spec.ts @@ -0,0 +1,88 @@ +import type { Locator, Page } from "@playwright/test" +import { test, expect } from "../fixtures" +import { promptAgentSelector, promptModelSelector, promptSelector } from "../selectors" + +type Probe = { + agent?: string + model?: { providerID: string; modelID: string; name?: string } + models?: Array<{ providerID: string; modelID: string; name: string }> + agents?: Array<{ name: string }> +} + +async function probe(page: Page): Promise { + return page.evaluate(() => { + const win = window as Window & { + __opencode_e2e?: { + model?: { + current?: Probe + } + } + } + return win.__opencode_e2e?.model?.current ?? null + }) +} + +async function state(page: Page) { + const value = await probe(page) + if (!value) throw new Error("Failed to resolve model selection probe") + return value +} + +async function ready(page: Page) { + const prompt = page.locator(promptSelector) + await prompt.click() + await expect(prompt).toBeFocused() + await prompt.pressSequentially("focus") + return prompt +} + +async function body(prompt: Locator) { + return prompt.evaluate((el) => (el as HTMLElement).innerText) +} + +test("agent select returns focus to the prompt", async ({ page, gotoSession }) => { + await gotoSession() + + const prompt = await ready(page) + + const info = await state(page) + const next = info.agents?.map((item) => item.name).find((name) => name !== info.agent) + test.skip(!next, "only one agent available") + if (!next) return + + await page.locator(`${promptAgentSelector} [data-slot="select-select-trigger"]`).first().click() + + const item = page.locator('[data-slot="select-select-item"]').filter({ hasText: next }).first() + await expect(item).toBeVisible() + await item.click({ force: true }) + + await expect(page.locator(`${promptAgentSelector} [data-slot="select-select-trigger-value"]`).first()).toHaveText( + next, + ) + await expect(prompt).toBeFocused() + await prompt.pressSequentially(" agent") + await expect.poll(() => body(prompt)).toContain("focus agent") +}) + +test("model select returns focus to the prompt", async ({ page, gotoSession }) => { + await gotoSession() + + const prompt = await ready(page) + + const info = await state(page) + const key = info.model ? `${info.model.providerID}:${info.model.modelID}` : null + const next = info.models?.find((item) => `${item.providerID}:${item.modelID}` !== key) + test.skip(!next, "only one model available") + if (!next) return + + await page.locator(`${promptModelSelector} [data-action="prompt-model"]`).first().click() + + const item = page.locator(`[data-slot="list-item"][data-key="${next.providerID}:${next.modelID}"]`).first() + await expect(item).toBeVisible() + await item.click({ force: true }) + + await expect(page.locator(`${promptModelSelector} [data-action="prompt-model"] span`).first()).toHaveText(next.name) + await expect(prompt).toBeFocused() + await prompt.pressSequentially(" model") + await expect.poll(() => body(prompt)).toContain("focus model") +}) diff --git a/packages/app/src/components/dialog-select-model.tsx b/packages/app/src/components/dialog-select-model.tsx index cb688c30a64d..fdef866a79d9 100644 --- a/packages/app/src/components/dialog-select-model.tsx +++ b/packages/app/src/components/dialog-select-model.tsx @@ -86,6 +86,7 @@ const ModelList: Component<{ } type ModelSelectorTriggerProps = Omit, "as" | "ref"> +type Dismiss = "escape" | "outside" | "select" | "manage" | "provider" export function ModelSelectorPopover(props: { provider?: string @@ -93,25 +94,31 @@ export function ModelSelectorPopover(props: { children?: JSX.Element triggerAs?: ValidComponent triggerProps?: ModelSelectorTriggerProps + onClose?: (cause: "escape" | "select") => void }) { const [store, setStore] = createStore<{ open: boolean - dismiss: "escape" | "outside" | null + dismiss: Dismiss | null }>({ open: false, dismiss: null, }) const dialog = useDialog() - const handleManage = () => { + const close = (dismiss: Dismiss) => { + setStore("dismiss", dismiss) setStore("open", false) + } + + const handleManage = () => { + close("manage") void import("./dialog-manage-models").then((x) => { dialog.show(() => ) }) } const handleConnectProvider = () => { - setStore("open", false) + close("provider") void import("./dialog-select-provider").then((x) => { dialog.show(() => ) }) @@ -136,21 +143,19 @@ export function ModelSelectorPopover(props: { { - setStore("dismiss", "escape") - setStore("open", false) + close("escape") event.preventDefault() event.stopPropagation() }} - onPointerDownOutside={() => { - setStore("dismiss", "outside") - setStore("open", false) - }} - onFocusOutside={() => { - setStore("dismiss", "outside") - setStore("open", false) - }} + onPointerDownOutside={() => close("outside")} + onFocusOutside={() => close("outside")} onCloseAutoFocus={(event) => { - if (store.dismiss === "outside") event.preventDefault() + const dismiss = store.dismiss + if (dismiss === "outside") event.preventDefault() + if (dismiss === "escape" || dismiss === "select") { + event.preventDefault() + props.onClose?.(dismiss) + } setStore("dismiss", null) }} > @@ -158,7 +163,7 @@ export function ModelSelectorPopover(props: { setStore("open", false)} + onSelect={() => close("select")} class="p-1" action={

diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 653e89f51141..ff31c8c2d912 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -502,6 +502,15 @@ export const PromptInput: Component = (props) => { return getCursorPosition(editorRef) } + const restoreFocus = () => { + requestAnimationFrame(() => { + const cursor = prompt.cursor() ?? promptLength(prompt.current()) + editorRef.focus() + setCursorPosition(editorRef, cursor) + queueScroll() + }) + } + const renderEditorWithCursor = (parts: Prompt) => { const cursor = currentCursor() renderEditor(parts) @@ -1471,7 +1480,10 @@ export const PromptInput: Component = (props) => { size="normal" options={agentNames()} current={local.agent.current()?.name ?? ""} - onSelect={local.agent.set} + onSelect={(value) => { + local.agent.set(value) + restoreFocus() + }} class="capitalize max-w-[160px] text-text-base" valueClass="truncate text-13-regular text-text-base" triggerStyle={control()} @@ -1535,6 +1547,7 @@ export const PromptInput: Component = (props) => { class: "min-w-0 max-w-[320px] text-13-regular text-text-base group", "data-action": "prompt-model", }} + onClose={restoreFocus} > = (props) => { options={variants()} current={local.model.variant.current() ?? "default"} label={(x) => (x === "default" ? language.t("common.default") : x)} - onSelect={(x) => local.model.variant.set(x === "default" ? undefined : x)} + onSelect={(value) => { + local.model.variant.set(value === "default" ? undefined : value) + restoreFocus() + }} class="capitalize max-w-[160px] text-text-base" valueClass="truncate text-13-regular text-text-base" triggerStyle={control()} From 070b911428ff77d8292b4daa90e294620bd3ad81 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Fri, 3 Apr 2026 20:24:57 +0530 Subject: [PATCH 063/616] feat: restore git-backed review modes (#20845) --- .../app/src/context/global-sync/bootstrap.ts | 2 +- .../context/global-sync/event-reducer.test.ts | 10 +- .../src/context/global-sync/event-reducer.ts | 4 +- packages/app/src/i18n/en.ts | 2 + packages/app/src/pages/session.tsx | 272 +++++++++++++--- .../src/pages/session/session-side-panel.tsx | 58 ++-- .../pages/session/use-session-commands.tsx | 6 +- packages/opencode/src/cli/cmd/github.ts | 10 +- packages/opencode/src/cli/cmd/pr.ts | 8 +- packages/opencode/src/file/index.ts | 14 +- packages/opencode/src/file/watcher.ts | 4 +- packages/opencode/src/git/index.ts | 303 ++++++++++++++++++ packages/opencode/src/project/vcs.ts | 182 +++++++++-- packages/opencode/src/server/instance.ts | 33 +- packages/opencode/src/storage/storage.ts | 4 +- packages/opencode/src/util/git.ts | 35 -- packages/opencode/src/worktree/index.ts | 51 +-- packages/opencode/test/git/git.test.ts | 128 ++++++++ packages/opencode/test/project/vcs.test.ts | 132 +++++++- packages/sdk/js/src/v2/gen/sdk.gen.ts | 33 ++ packages/sdk/js/src/v2/gen/types.gen.ts | 21 ++ packages/ui/src/components/session-review.tsx | 35 +- packages/ui/src/i18n/en.ts | 2 + 23 files changed, 1102 insertions(+), 247 deletions(-) create mode 100644 packages/opencode/src/git/index.ts delete mode 100644 packages/opencode/src/util/git.ts create mode 100644 packages/opencode/test/git/git.test.ts diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index cf104ad97fc4..7edd5a1ce106 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -248,7 +248,7 @@ export async function bootstrapDirectory(input: { input.sdk.vcs.get().then((x) => { const next = x.data ?? input.store.vcs input.setStore("vcs", next) - if (next?.branch) input.vcsCache.setStore("value", next) + if (next) input.vcsCache.setStore("value", next) }), ), () => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))), diff --git a/packages/app/src/context/global-sync/event-reducer.test.ts b/packages/app/src/context/global-sync/event-reducer.test.ts index cf2da135cbbc..892129788e6c 100644 --- a/packages/app/src/context/global-sync/event-reducer.test.ts +++ b/packages/app/src/context/global-sync/event-reducer.test.ts @@ -494,8 +494,10 @@ describe("applyDirectoryEvent", () => { }) test("updates vcs branch in store and cache", () => { - const [store, setStore] = createStore(baseState()) - const [cacheStore, setCacheStore] = createStore({ value: undefined as State["vcs"] }) + const [store, setStore] = createStore(baseState({ vcs: { branch: "main", default_branch: "main" } })) + const [cacheStore, setCacheStore] = createStore({ + value: { branch: "main", default_branch: "main" } as State["vcs"], + }) applyDirectoryEvent({ event: { type: "vcs.branch.updated", properties: { branch: "feature/test" } }, @@ -511,8 +513,8 @@ describe("applyDirectoryEvent", () => { }, }) - expect(store.vcs).toEqual({ branch: "feature/test" }) - expect(cacheStore.value).toEqual({ branch: "feature/test" }) + expect(store.vcs).toEqual({ branch: "feature/test", default_branch: "main" }) + expect(cacheStore.value).toEqual({ branch: "feature/test", default_branch: "main" }) }) test("routes disposal and lsp events to side-effect handlers", () => { diff --git a/packages/app/src/context/global-sync/event-reducer.ts b/packages/app/src/context/global-sync/event-reducer.ts index 5d8b7c4e3d8e..4af636553526 100644 --- a/packages/app/src/context/global-sync/event-reducer.ts +++ b/packages/app/src/context/global-sync/event-reducer.ts @@ -271,9 +271,9 @@ export function applyDirectoryEvent(input: { break } case "vcs.branch.updated": { - const props = event.properties as { branch: string } + const props = event.properties as { branch?: string } if (input.store.vcs?.branch === props.branch) break - const next = { branch: props.branch } + const next = { ...input.store.vcs, branch: props.branch } input.setStore("vcs", next) if (input.vcsCache) input.vcsCache.setStore("value", next) break diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 39317b8d657e..ace0efeb8714 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -535,6 +535,8 @@ export const dict = { "session.review.noVcs.createGit.action": "Create Git repository", "session.review.noSnapshot": "Snapshot tracking is disabled in config, so session changes are unavailable", "session.review.noChanges": "No changes", + "session.review.noUncommittedChanges": "No uncommitted changes yet", + "session.review.noBranchChanges": "No branch changes yet", "session.files.selectToOpen": "Select a file to open", "session.files.all": "All files", diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index ae895adbe9b4..a81df9dd2779 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1,4 +1,4 @@ -import type { Project, UserMessage } from "@opencode-ai/sdk/v2" +import type { FileDiff, Project, UserMessage } from "@opencode-ai/sdk/v2" import { useDialog } from "@opencode-ai/ui/context/dialog" import { useMutation } from "@tanstack/solid-query" import { @@ -68,6 +68,9 @@ type FollowupItem = FollowupDraft & { id: string } type FollowupEdit = Pick const emptyFollowups: FollowupItem[] = [] +type ChangeMode = "git" | "branch" | "session" | "turn" +type VcsMode = "git" | "branch" + type SessionHistoryWindowInput = { sessionID: () => string | undefined messagesReady: () => boolean @@ -427,15 +430,16 @@ export default function Page() { const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : [])) - const reviewCount = createMemo(() => Math.max(info()?.summary?.files ?? 0, diffs().length)) - const hasReview = createMemo(() => reviewCount() > 0) + const sessionCount = createMemo(() => Math.max(info()?.summary?.files ?? 0, diffs().length)) + const hasSessionReview = createMemo(() => sessionCount() > 0) + const canReview = createMemo(() => !!sync.project) const reviewTab = createMemo(() => isDesktop()) const tabState = createSessionTabs({ tabs, pathFromTab: file.pathFromTab, normalizeTab, review: reviewTab, - hasReview, + hasReview: canReview, }) const contextOpen = tabState.contextOpen const openedTabs = tabState.openedTabs @@ -458,6 +462,12 @@ export default function Page() { if (!id) return false return sync.session.history.loading(id) }) + const diffsReady = createMemo(() => { + const id = params.id + if (!id) return true + if (!hasSessionReview()) return true + return sync.data.session_diff[id] !== undefined + }) const userMessages = createMemo( () => messages().filter((m) => m.role === "user") as UserMessage[], @@ -511,11 +521,22 @@ export default function Page() { const [store, setStore] = createStore({ messageId: undefined as string | undefined, mobileTab: "session" as "session" | "changes", - changes: "session" as "session" | "turn", + changes: "git" as ChangeMode, newSessionWorktree: "main", deferRender: false, }) + const [vcs, setVcs] = createStore({ + diff: { + git: [] as FileDiff[], + branch: [] as FileDiff[], + }, + ready: { + git: false, + branch: false, + }, + }) + const [followup, setFollowup] = persisted( Persist.workspace(sdk.directory, "followup", ["followup.v1"]), createStore<{ @@ -549,6 +570,68 @@ export default function Page() { let todoTimer: number | undefined let diffFrame: number | undefined let diffTimer: number | undefined + const vcsTask = new Map>() + const vcsRun = new Map() + + const bumpVcs = (mode: VcsMode) => { + const next = (vcsRun.get(mode) ?? 0) + 1 + vcsRun.set(mode, next) + return next + } + + const resetVcs = (mode?: VcsMode) => { + const list = mode ? [mode] : (["git", "branch"] as const) + list.forEach((item) => { + bumpVcs(item) + vcsTask.delete(item) + setVcs("diff", item, []) + setVcs("ready", item, false) + }) + } + + const loadVcs = (mode: VcsMode, force = false) => { + if (sync.project?.vcs !== "git") return Promise.resolve() + if (!force && vcs.ready[mode]) return Promise.resolve() + + if (force) { + if (vcsTask.has(mode)) bumpVcs(mode) + vcsTask.delete(mode) + setVcs("ready", mode, false) + } + + const current = vcsTask.get(mode) + if (current) return current + + const run = bumpVcs(mode) + + const task = sdk.client.vcs + .diff({ mode }) + .then((result) => { + if (vcsRun.get(mode) !== run) return + setVcs("diff", mode, result.data ?? []) + setVcs("ready", mode, true) + }) + .catch((error) => { + if (vcsRun.get(mode) !== run) return + console.debug("[session-review] failed to load vcs diff", { mode, error }) + setVcs("diff", mode, []) + setVcs("ready", mode, true) + }) + .finally(() => { + if (vcsTask.get(mode) === task) vcsTask.delete(mode) + }) + + vcsTask.set(mode, task) + return task + } + + const refreshVcs = () => { + resetVcs() + const mode = untrack(vcsMode) + if (!mode) return + if (!untrack(wantsReview)) return + void loadVcs(mode, true) + } createComputed((prev) => { const open = desktopReviewOpen() @@ -564,7 +647,42 @@ export default function Page() { }, desktopReviewOpen()) const turnDiffs = createMemo(() => lastUserMessage()?.summary?.diffs ?? []) - const reviewDiffs = createMemo(() => (store.changes === "session" ? diffs() : turnDiffs())) + const changesOptions = createMemo(() => { + const list: ChangeMode[] = [] + if (sync.project?.vcs === "git") list.push("git") + if ( + sync.project?.vcs === "git" && + sync.data.vcs?.branch && + sync.data.vcs?.default_branch && + sync.data.vcs.branch !== sync.data.vcs.default_branch + ) { + list.push("branch") + } + list.push("session", "turn") + return list + }) + const vcsMode = createMemo(() => { + if (store.changes === "git" || store.changes === "branch") return store.changes + }) + const reviewDiffs = createMemo(() => { + if (store.changes === "git") return vcs.diff.git + if (store.changes === "branch") return vcs.diff.branch + if (store.changes === "session") return diffs() + return turnDiffs() + }) + const reviewCount = createMemo(() => { + if (store.changes === "git") return vcs.diff.git.length + if (store.changes === "branch") return vcs.diff.branch.length + if (store.changes === "session") return sessionCount() + return turnDiffs().length + }) + const hasReview = createMemo(() => reviewCount() > 0) + const reviewReady = createMemo(() => { + if (store.changes === "git") return vcs.ready.git + if (store.changes === "branch") return vcs.ready.branch + if (store.changes === "session") return !hasSessionReview() || diffsReady() + return true + }) const newSessionWorktree = createMemo(() => { if (store.newSessionWorktree === "create") return "create" @@ -630,13 +748,7 @@ export default function Page() { scrollToMessage(msgs[targetIndex], "auto") } - const diffsReady = createMemo(() => { - const id = params.id - if (!id) return true - if (!hasReview()) return true - return sync.data.session_diff[id] !== undefined - }) - const reviewEmptyKey = createMemo(() => { + const sessionEmptyKey = createMemo(() => { const project = sync.project if (project && !project.vcs) return "session.review.noVcs" if (sync.data.config.snapshot === false) return "session.review.noSnapshot" @@ -790,13 +902,46 @@ export default function Page() { sessionKey, () => { setStore("messageId", undefined) - setStore("changes", "session") + setStore("changes", "git") setUi("pendingMessage", undefined) }, { defer: true }, ), ) + createEffect( + on( + () => sdk.directory, + () => { + resetVcs() + }, + { defer: true }, + ), + ) + + createEffect( + on( + () => [sync.data.vcs?.branch, sync.data.vcs?.default_branch] as const, + (next, prev) => { + if (prev === undefined || same(next, prev)) return + refreshVcs() + }, + { defer: true }, + ), + ) + + const stopVcs = sdk.event.listen((evt) => { + if (evt.details.type !== "file.watcher.updated") return + const props = + typeof evt.details.properties === "object" && evt.details.properties + ? (evt.details.properties as Record) + : undefined + const file = typeof props?.file === "string" ? props.file : undefined + if (!file || file.startsWith(".git/")) return + refreshVcs() + }) + onCleanup(stopVcs) + createEffect( on( () => params.dir, @@ -919,6 +1064,40 @@ export default function Page() { } const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes") + const wantsReview = createMemo(() => + isDesktop() + ? desktopFileTreeOpen() || (desktopReviewOpen() && activeTab() === "review") + : store.mobileTab === "changes", + ) + + createEffect(() => { + const list = changesOptions() + if (list.includes(store.changes)) return + const next = list[0] + if (!next) return + setStore("changes", next) + }) + + createEffect(() => { + const mode = vcsMode() + if (!mode) return + if (!wantsReview()) return + void loadVcs(mode) + }) + + createEffect( + on( + () => sync.data.session_status[params.id ?? ""]?.type, + (next, prev) => { + const mode = vcsMode() + if (!mode) return + if (!wantsReview()) return + if (next !== "idle" || prev === undefined || prev === "idle") return + void loadVcs(mode, true) + }, + { defer: true }, + ), + ) const fileTreeTab = () => layout.fileTree.tab() const setFileTreeTab = (value: "changes" | "all") => layout.fileTree.setTab(value) @@ -965,21 +1144,23 @@ export default function Page() { loadFile: file.load, }) - const changesOptions = ["session", "turn"] as const - const changesOptionsList = [...changesOptions] - const changesTitle = () => { - if (!hasReview()) { + if (!canReview()) { return null } + const label = (option: ChangeMode) => { + if (option === "git") return language.t("ui.sessionReview.title.git") + if (option === "branch") return language.t("ui.sessionReview.title.branch") + if (option === "session") return language.t("ui.sessionReview.title") + return language.t("ui.sessionReview.title.lastTurn") + } + return ( o.value === settings.general.followup())} - value={(o) => o.value} - label={(o) => o.label} - onSelect={(option) => option && settings.general.setFollowup(option.value)} - variant="secondary" - size="small" - triggerVariant="settings" - triggerStyle={{ "min-width": "180px" }} - /> -
) diff --git a/packages/app/src/context/settings.tsx b/packages/app/src/context/settings.tsx index ae7768f71a87..afd03365eacc 100644 --- a/packages/app/src/context/settings.tsx +++ b/packages/app/src/context/settings.tsx @@ -136,6 +136,11 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont root.style.setProperty("--font-family-sans", sansFontFamily(store.appearance?.sans)) }) + createEffect(() => { + if (store.general?.followup !== "queue") return + setStore("general", "followup", "steer") + }) + return { ready, get current() { @@ -150,9 +155,12 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont setReleaseNotes(value: boolean) { setStore("general", "releaseNotes", value) }, - followup: withFallback(() => store.general?.followup, defaultSettings.general.followup), + followup: withFallback( + () => (store.general?.followup === "queue" ? "steer" : store.general?.followup), + defaultSettings.general.followup, + ), setFollowup(value: "queue" | "steer") { - setStore("general", "followup", value) + setStore("general", "followup", value === "queue" ? "steer" : value) }, showReasoningSummaries: withFallback( () => store.general?.showReasoningSummaries, From ca73f2fbf989334f871ff3ac1186bcc4d4265ff8 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 3 Apr 2026 12:05:40 -0400 Subject: [PATCH 067/616] refactor(effect): build todowrite tool from Todo service (#20789) Co-authored-by: Juan Pablo Carranza Hurtado <52012198+jpcarranza94@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 --- packages/opencode/src/session/todo.ts | 2 +- packages/opencode/src/tool/registry.ts | 304 +++++++++--------- packages/opencode/src/tool/todo.ts | 63 ++-- .../test/session/prompt-effect.test.ts | 8 +- .../test/session/snapshot-tool-race.test.ts | 8 +- .../opencode/test/tool/tool-define.test.ts | 30 +- 6 files changed, 219 insertions(+), 196 deletions(-) diff --git a/packages/opencode/src/session/todo.ts b/packages/opencode/src/session/todo.ts index 8bb5dc522ad6..2d85ad224f1b 100644 --- a/packages/opencode/src/session/todo.ts +++ b/packages/opencode/src/session/todo.ts @@ -82,7 +82,7 @@ export namespace Todo { }), ) - const defaultLayer = layer.pipe(Layer.provide(Bus.layer)) + export const defaultLayer = layer.pipe(Layer.provide(Bus.layer)) const { runPromise } = makeRuntime(Service, defaultLayer) export async function update(input: { sessionID: SessionID; todos: Info[] }) { diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 8e21c43e3cd9..b538a9e88018 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -34,6 +34,7 @@ import { InstanceState } from "@/effect/instance-state" import { makeRuntime } from "@/effect/run-service" import { Env } from "../env" import { Question } from "../question" +import { Todo } from "../session/todo" export namespace ToolRegistry { const log = Log.create({ service: "tool.registry" }) @@ -56,165 +57,167 @@ export namespace ToolRegistry { export class Service extends ServiceMap.Service()("@opencode/ToolRegistry") {} - export const layer: Layer.Layer = Layer.effect( - Service, - Effect.gen(function* () { - const config = yield* Config.Service - const plugin = yield* Plugin.Service - - const build = (tool: T | Effect.Effect) => - Effect.isEffect(tool) ? tool : Effect.succeed(tool) - - const state = yield* InstanceState.make( - Effect.fn("ToolRegistry.state")(function* (ctx) { - const custom: Tool.Info[] = [] - - function fromPlugin(id: string, def: ToolDefinition): Tool.Info { - return { - id, - init: async (initCtx) => ({ - parameters: z.object(def.args), - description: def.description, - execute: async (args, toolCtx) => { - const pluginCtx = { - ...toolCtx, - directory: ctx.directory, - worktree: ctx.worktree, - } as unknown as PluginToolContext - const result = await def.execute(args as any, pluginCtx) - const out = await Truncate.output(result, {}, initCtx?.agent) - return { - title: "", - output: out.truncated ? out.content : result, - metadata: { truncated: out.truncated, outputPath: out.truncated ? out.outputPath : undefined }, - } - }, - }), + export const layer: Layer.Layer = + Layer.effect( + Service, + Effect.gen(function* () { + const config = yield* Config.Service + const plugin = yield* Plugin.Service + + const build = (tool: T | Effect.Effect) => + Effect.isEffect(tool) ? tool : Effect.succeed(tool) + + const state = yield* InstanceState.make( + Effect.fn("ToolRegistry.state")(function* (ctx) { + const custom: Tool.Info[] = [] + + function fromPlugin(id: string, def: ToolDefinition): Tool.Info { + return { + id, + init: async (initCtx) => ({ + parameters: z.object(def.args), + description: def.description, + execute: async (args, toolCtx) => { + const pluginCtx = { + ...toolCtx, + directory: ctx.directory, + worktree: ctx.worktree, + } as unknown as PluginToolContext + const result = await def.execute(args as any, pluginCtx) + const out = await Truncate.output(result, {}, initCtx?.agent) + return { + title: "", + output: out.truncated ? out.content : result, + metadata: { truncated: out.truncated, outputPath: out.truncated ? out.outputPath : undefined }, + } + }, + }), + } } - } - const dirs = yield* config.directories() - const matches = dirs.flatMap((dir) => - Glob.scanSync("{tool,tools}/*.{js,ts}", { cwd: dir, absolute: true, dot: true, symlink: true }), - ) - if (matches.length) yield* config.waitForDependencies() - for (const match of matches) { - const namespace = path.basename(match, path.extname(match)) - const mod = yield* Effect.promise( - () => import(process.platform === "win32" ? match : pathToFileURL(match).href), + const dirs = yield* config.directories() + const matches = dirs.flatMap((dir) => + Glob.scanSync("{tool,tools}/*.{js,ts}", { cwd: dir, absolute: true, dot: true, symlink: true }), ) - for (const [id, def] of Object.entries(mod)) { - custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def)) + if (matches.length) yield* config.waitForDependencies() + for (const match of matches) { + const namespace = path.basename(match, path.extname(match)) + const mod = yield* Effect.promise( + () => import(process.platform === "win32" ? match : pathToFileURL(match).href), + ) + for (const [id, def] of Object.entries(mod)) { + custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def)) + } } - } - const plugins = yield* plugin.list() - for (const p of plugins) { - for (const [id, def] of Object.entries(p.tool ?? {})) { - custom.push(fromPlugin(id, def)) - } - } - - return { custom } - }), - ) - - const invalid = yield* build(InvalidTool) - const ask = yield* build(QuestionTool) - const bash = yield* build(BashTool) - const read = yield* build(ReadTool) - const glob = yield* build(GlobTool) - const grep = yield* build(GrepTool) - const edit = yield* build(EditTool) - const write = yield* build(WriteTool) - const task = yield* build(TaskTool) - const fetch = yield* build(WebFetchTool) - const todo = yield* build(TodoWriteTool) - const search = yield* build(WebSearchTool) - const code = yield* build(CodeSearchTool) - const skill = yield* build(SkillTool) - const patch = yield* build(ApplyPatchTool) - const lsp = yield* build(LspTool) - const batch = yield* build(BatchTool) - const plan = yield* build(PlanExitTool) - - const all = Effect.fn("ToolRegistry.all")(function* (custom: Tool.Info[]) { - const cfg = yield* config.get() - const question = ["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || Flag.OPENCODE_ENABLE_QUESTION_TOOL - - return [ - invalid, - ...(question ? [ask] : []), - bash, - read, - glob, - grep, - edit, - write, - task, - fetch, - todo, - search, - code, - skill, - patch, - ...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [lsp] : []), - ...(cfg.experimental?.batch_tool === true ? [batch] : []), - ...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [plan] : []), - ...custom, - ] - }) - - const ids = Effect.fn("ToolRegistry.ids")(function* () { - const s = yield* InstanceState.get(state) - const tools = yield* all(s.custom) - return tools.map((t) => t.id) - }) - - const tools = Effect.fn("ToolRegistry.tools")(function* ( - model: { providerID: ProviderID; modelID: ModelID }, - agent?: Agent.Info, - ) { - const s = yield* InstanceState.get(state) - const allTools = yield* all(s.custom) - const filtered = allTools.filter((tool) => { - if (tool.id === "codesearch" || tool.id === "websearch") { - return model.providerID === ProviderID.opencode || Flag.OPENCODE_ENABLE_EXA - } - - const usePatch = - !!Env.get("OPENCODE_E2E_LLM_URL") || - (model.modelID.includes("gpt-") && !model.modelID.includes("oss") && !model.modelID.includes("gpt-4")) - if (tool.id === "apply_patch") return usePatch - if (tool.id === "edit" || tool.id === "write") return !usePatch - - return true - }) - return yield* Effect.forEach( - filtered, - Effect.fnUntraced(function* (tool: Tool.Info) { - using _ = log.time(tool.id) - const next = yield* Effect.promise(() => tool.init({ agent })) - const output = { - description: next.description, - parameters: next.parameters, - } - yield* plugin.trigger("tool.definition", { toolID: tool.id }, output) - return { - id: tool.id, - description: output.description, - parameters: output.parameters, - execute: next.execute, - formatValidationError: next.formatValidationError, + const plugins = yield* plugin.list() + for (const p of plugins) { + for (const [id, def] of Object.entries(p.tool ?? {})) { + custom.push(fromPlugin(id, def)) + } } + + return { custom } }), - { concurrency: "unbounded" }, ) - }) - return Service.of({ ids, named: { task, read }, tools }) - }), - ) + const invalid = yield* build(InvalidTool) + const ask = yield* build(QuestionTool) + const bash = yield* build(BashTool) + const read = yield* build(ReadTool) + const glob = yield* build(GlobTool) + const grep = yield* build(GrepTool) + const edit = yield* build(EditTool) + const write = yield* build(WriteTool) + const task = yield* build(TaskTool) + const fetch = yield* build(WebFetchTool) + const todo = yield* build(TodoWriteTool) + const search = yield* build(WebSearchTool) + const code = yield* build(CodeSearchTool) + const skill = yield* build(SkillTool) + const patch = yield* build(ApplyPatchTool) + const lsp = yield* build(LspTool) + const batch = yield* build(BatchTool) + const plan = yield* build(PlanExitTool) + + const all = Effect.fn("ToolRegistry.all")(function* (custom: Tool.Info[]) { + const cfg = yield* config.get() + const question = + ["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || Flag.OPENCODE_ENABLE_QUESTION_TOOL + + return [ + invalid, + ...(question ? [ask] : []), + bash, + read, + glob, + grep, + edit, + write, + task, + fetch, + todo, + search, + code, + skill, + patch, + ...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [lsp] : []), + ...(cfg.experimental?.batch_tool === true ? [batch] : []), + ...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [plan] : []), + ...custom, + ] + }) + + const ids = Effect.fn("ToolRegistry.ids")(function* () { + const s = yield* InstanceState.get(state) + const tools = yield* all(s.custom) + return tools.map((t) => t.id) + }) + + const tools = Effect.fn("ToolRegistry.tools")(function* ( + model: { providerID: ProviderID; modelID: ModelID }, + agent?: Agent.Info, + ) { + const s = yield* InstanceState.get(state) + const allTools = yield* all(s.custom) + const filtered = allTools.filter((tool) => { + if (tool.id === "codesearch" || tool.id === "websearch") { + return model.providerID === ProviderID.opencode || Flag.OPENCODE_ENABLE_EXA + } + + const usePatch = + !!Env.get("OPENCODE_E2E_LLM_URL") || + (model.modelID.includes("gpt-") && !model.modelID.includes("oss") && !model.modelID.includes("gpt-4")) + if (tool.id === "apply_patch") return usePatch + if (tool.id === "edit" || tool.id === "write") return !usePatch + + return true + }) + return yield* Effect.forEach( + filtered, + Effect.fnUntraced(function* (tool: Tool.Info) { + using _ = log.time(tool.id) + const next = yield* Effect.promise(() => tool.init({ agent })) + const output = { + description: next.description, + parameters: next.parameters, + } + yield* plugin.trigger("tool.definition", { toolID: tool.id }, output) + return { + id: tool.id, + description: output.description, + parameters: output.parameters, + execute: next.execute, + formatValidationError: next.formatValidationError, + } + }), + { concurrency: "unbounded" }, + ) + }) + + return Service.of({ ids, named: { task, read }, tools }) + }), + ) export const defaultLayer = Layer.unwrap( Effect.sync(() => @@ -222,6 +225,7 @@ export namespace ToolRegistry { Layer.provide(Config.defaultLayer), Layer.provide(Plugin.defaultLayer), Layer.provide(Question.defaultLayer), + Layer.provide(Todo.defaultLayer), ), ), ) diff --git a/packages/opencode/src/tool/todo.ts b/packages/opencode/src/tool/todo.ts index a5e56cb23e43..d10e84931ab0 100644 --- a/packages/opencode/src/tool/todo.ts +++ b/packages/opencode/src/tool/todo.ts @@ -1,31 +1,48 @@ import z from "zod" +import { Effect } from "effect" import { Tool } from "./tool" import DESCRIPTION_WRITE from "./todowrite.txt" import { Todo } from "../session/todo" -export const TodoWriteTool = Tool.define("todowrite", { - description: DESCRIPTION_WRITE, - parameters: z.object({ - todos: z.array(z.object(Todo.Info.shape)).describe("The updated todo list"), - }), - async execute(params, ctx) { - await ctx.ask({ - permission: "todowrite", - patterns: ["*"], - always: ["*"], - metadata: {}, - }) +const parameters = z.object({ + todos: z.array(z.object(Todo.Info.shape)).describe("The updated todo list"), +}) + +type Metadata = { + todos: Todo.Info[] +} + +export const TodoWriteTool = Tool.defineEffect( + "todowrite", + Effect.gen(function* () { + const todo = yield* Todo.Service - await Todo.update({ - sessionID: ctx.sessionID, - todos: params.todos, - }) return { - title: `${params.todos.filter((x) => x.status !== "completed").length} todos`, - output: JSON.stringify(params.todos, null, 2), - metadata: { - todos: params.todos, + description: DESCRIPTION_WRITE, + parameters, + async execute(params: z.infer, ctx: Tool.Context) { + await ctx.ask({ + permission: "todowrite", + patterns: ["*"], + always: ["*"], + metadata: {}, + }) + + await todo + .update({ + sessionID: ctx.sessionID, + todos: params.todos, + }) + .pipe(Effect.runPromise) + + return { + title: `${params.todos.filter((x) => x.status !== "completed").length} todos`, + output: JSON.stringify(params.todos, null, 2), + metadata: { + todos: params.todos, + }, + } }, - } - }, -}) + } satisfies Tool.Def + }), +) diff --git a/packages/opencode/test/session/prompt-effect.test.ts b/packages/opencode/test/session/prompt-effect.test.ts index a6fd1f27dbe3..17689cf274ec 100644 --- a/packages/opencode/test/session/prompt-effect.test.ts +++ b/packages/opencode/test/session/prompt-effect.test.ts @@ -16,6 +16,7 @@ import { Provider as ProviderSvc } from "../../src/provider/provider" import type { Provider } from "../../src/provider/provider" import { ModelID, ProviderID } from "../../src/provider/schema" import { Question } from "../../src/question" +import { Todo } from "../../src/session/todo" import { Session } from "../../src/session" import { LLM } from "../../src/session/llm" import { MessageV2 } from "../../src/session/message-v2" @@ -162,7 +163,12 @@ function makeHttp() { status, ).pipe(Layer.provideMerge(infra)) const question = Question.layer.pipe(Layer.provideMerge(deps)) - const registry = ToolRegistry.layer.pipe(Layer.provideMerge(question), Layer.provideMerge(deps)) + const todo = Todo.layer.pipe(Layer.provideMerge(deps)) + const registry = ToolRegistry.layer.pipe( + Layer.provideMerge(todo), + Layer.provideMerge(question), + Layer.provideMerge(deps), + ) const trunc = Truncate.layer.pipe(Layer.provideMerge(deps)) const proc = SessionProcessor.layer.pipe(Layer.provideMerge(deps)) const compact = SessionCompaction.layer.pipe(Layer.provideMerge(proc), Layer.provideMerge(deps)) diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 019cf1a796dd..c192a446bd49 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -39,6 +39,7 @@ import { Permission } from "../../src/permission" import { Plugin } from "../../src/plugin" import { Provider as ProviderSvc } from "../../src/provider/provider" import { Question } from "../../src/question" +import { Todo } from "../../src/session/todo" import { SessionCompaction } from "../../src/session/compaction" import { Instruction } from "../../src/session/instruction" import { SessionProcessor } from "../../src/session/processor" @@ -126,7 +127,12 @@ function makeHttp() { status, ).pipe(Layer.provideMerge(infra)) const question = Question.layer.pipe(Layer.provideMerge(deps)) - const registry = ToolRegistry.layer.pipe(Layer.provideMerge(question), Layer.provideMerge(deps)) + const todo = Todo.layer.pipe(Layer.provideMerge(deps)) + const registry = ToolRegistry.layer.pipe( + Layer.provideMerge(todo), + Layer.provideMerge(question), + Layer.provideMerge(deps), + ) const trunc = Truncate.layer.pipe(Layer.provideMerge(deps)) const proc = SessionProcessor.layer.pipe(Layer.provideMerge(deps)) const compact = SessionCompaction.layer.pipe(Layer.provideMerge(proc), Layer.provideMerge(deps)) diff --git a/packages/opencode/test/tool/tool-define.test.ts b/packages/opencode/test/tool/tool-define.test.ts index b3ed66c93542..1503eed728f3 100644 --- a/packages/opencode/test/tool/tool-define.test.ts +++ b/packages/opencode/test/tool/tool-define.test.ts @@ -27,45 +27,37 @@ describe("Tool.define", () => { await tool.init() await tool.init() - // The original object's execute should never be overwritten expect(original.execute).toBe(originalExecute) }) test("object-defined tool does not accumulate wrapper layers across init() calls", async () => { - let executeCalls = 0 + let calls = 0 const tool = Tool.define( "test-tool", - makeTool("test", () => executeCalls++), + makeTool("test", () => calls++), ) - // Call init() many times to simulate many agentic steps for (let i = 0; i < 100; i++) { await tool.init() } - // Resolve the tool and call execute const resolved = await tool.init() - executeCalls = 0 + calls = 0 - // Capture the stack trace inside execute to measure wrapper depth - let stackInsideExecute = "" - const origExec = resolved.execute + let stack = "" + const exec = resolved.execute resolved.execute = async (args: any, ctx: any) => { - const result = await origExec.call(resolved, args, ctx) - const err = new Error() - stackInsideExecute = err.stack || "" + const result = await exec.call(resolved, args, ctx) + stack = new Error().stack || "" return result } await resolved.execute(defaultArgs, {} as any) - expect(executeCalls).toBe(1) + expect(calls).toBe(1) - // Count how many times tool.ts appears in the stack. - // With the fix: 1 wrapper layer (from the most recent init()). - // Without the fix: 101 wrapper layers from accumulated closures. - const toolTsFrames = stackInsideExecute.split("\n").filter((l) => l.includes("tool.ts")).length - expect(toolTsFrames).toBeLessThan(5) + const frames = stack.split("\n").filter((l) => l.includes("tool.ts")).length + expect(frames).toBeLessThan(5) }) test("function-defined tool returns fresh objects and is unaffected", async () => { @@ -74,7 +66,6 @@ describe("Tool.define", () => { const first = await tool.init() const second = await tool.init() - // Function-defined tools return distinct objects each time expect(first).not.toBe(second) }) @@ -84,7 +75,6 @@ describe("Tool.define", () => { const first = await tool.init() const second = await tool.init() - // Each init() should return a separate object so wrappers don't accumulate expect(first).not.toBe(second) }) From ec9930cf4c34e010e0c771667196bbed05c13890 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 3 Apr 2026 12:12:01 -0400 Subject: [PATCH 068/616] test(ci): publish unit reports in actions (#20547) --- .github/workflows/test.yml | 34 ++++++++++++++++++++++++++++++---- packages/app/package.json | 1 + packages/opencode/package.json | 1 + turbo.json | 10 ++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 803093fc5953..119d652ae965 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,6 +15,7 @@ concurrency: permissions: contents: read + checks: write jobs: unit: @@ -45,14 +46,39 @@ jobs: git config --global user.email "bot@opencode.ai" git config --global user.name "opencode" + - name: Cache Turbo + uses: actions/cache@v4 + with: + path: node_modules/.cache/turbo + key: turbo-${{ runner.os }}-${{ hashFiles('turbo.json', '**/package.json') }}-${{ github.sha }} + restore-keys: | + turbo-${{ runner.os }}-${{ hashFiles('turbo.json', '**/package.json') }}- + turbo-${{ runner.os }}- + - name: Run unit tests - run: bun turbo test + run: bun turbo test:ci env: - # Bun 1.3.11 intermittently crashes on Windows during test teardown - # inside the native @parcel/watcher binding. Unit CI does not rely on - # the live watcher backend there, so disable it for that platform. OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }} + - name: Publish unit reports + if: always() + uses: mikepenz/action-junit-report@v6 + with: + report_paths: packages/*/.artifacts/unit/junit.xml + check_name: "unit results (${{ matrix.settings.name }})" + detailed_summary: true + include_time_in_summary: true + fail_on_failure: false + + - name: Upload unit artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: unit-${{ matrix.settings.name }}-${{ github.run_attempt }} + if-no-files-found: ignore + retention-days: 7 + path: packages/*/.artifacts/unit/junit.xml + e2e: name: e2e (${{ matrix.settings.name }}) strategy: diff --git a/packages/app/package.json b/packages/app/package.json index d179e4a52415..62749e209a2a 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -15,6 +15,7 @@ "build": "vite build", "serve": "vite preview", "test": "bun run test:unit", + "test:ci": "bun test --preload ./happydom.ts ./src --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml", "test:unit": "bun test --preload ./happydom.ts ./src", "test:unit:watch": "bun test --watch --preload ./happydom.ts ./src", "test:e2e": "playwright test", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index d4210574b77d..54b40b31b882 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -9,6 +9,7 @@ "prepare": "effect-language-service patch || true", "typecheck": "tsgo --noEmit", "test": "bun test --timeout 30000", + "test:ci": "bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml", "build": "bun run script/build.ts", "upgrade-opentui": "bun run script/upgrade-opentui.ts", "dev": "bun run --conditions=browser ./src/index.ts", diff --git a/turbo.json b/turbo.json index cff3381ecb7d..28c2fa2de0d2 100644 --- a/turbo.json +++ b/turbo.json @@ -13,9 +13,19 @@ "outputs": [], "passThroughEnv": ["*"] }, + "opencode#test:ci": { + "dependsOn": ["^build"], + "outputs": [".artifacts/unit/junit.xml"], + "passThroughEnv": ["*"] + }, "@opencode-ai/app#test": { "dependsOn": ["^build"], "outputs": [] + }, + "@opencode-ai/app#test:ci": { + "dependsOn": ["^build"], + "outputs": [".artifacts/unit/junit.xml"], + "passThroughEnv": ["*"] } } } From ba027fc10f58729adafe02c990d784bbe5e61a2f Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:44:50 -0500 Subject: [PATCH 069/616] chore: rm models snapshot (#20929) --- .../opencode/src/provider/models-snapshot.ts | 60410 ---------------- 1 file changed, 60410 deletions(-) delete mode 100644 packages/opencode/src/provider/models-snapshot.ts diff --git a/packages/opencode/src/provider/models-snapshot.ts b/packages/opencode/src/provider/models-snapshot.ts deleted file mode 100644 index 66bf3d1fa987..000000000000 --- a/packages/opencode/src/provider/models-snapshot.ts +++ /dev/null @@ -1,60410 +0,0 @@ -// Auto-generated by build.ts - do not edit -export const snapshot = { - evroc: { - id: "evroc", - env: ["EVROC_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://models.think.evroc.com/v1", - name: "evroc", - doc: "https://docs.evroc.com/products/think/overview.html", - models: { - "nvidia/Llama-3.3-70B-Instruct-FP8": { - id: "nvidia/Llama-3.3-70B-Instruct-FP8", - name: "Llama 3.3 70B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.18, output: 1.18 }, - limit: { context: 131072, output: 32768 }, - }, - "microsoft/Phi-4-multimodal-instruct": { - id: "microsoft/Phi-4-multimodal-instruct", - name: "Phi-4 15B", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.24, output: 0.47 }, - limit: { context: 32000, output: 32000 }, - }, - "intfloat/multilingual-e5-large-instruct": { - id: "intfloat/multilingual-e5-large-instruct", - name: "E5 Multi-Lingual Large Embeddings 0.6B", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2024-06-01", - last_updated: "2024-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.12, output: 0.12 }, - limit: { context: 512, output: 512 }, - }, - "moonshotai/Kimi-K2.5": { - id: "moonshotai/Kimi-K2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 1.47, output: 5.9 }, - limit: { context: 262144, output: 262144 }, - }, - "KBLab/kb-whisper-large": { - id: "KBLab/kb-whisper-large", - name: "KB Whisper", - family: "whisper", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2024-10-01", - last_updated: "2024-10-01", - modalities: { input: ["audio"], output: ["text"] }, - open_weights: true, - cost: { input: 0.00236, output: 0.00236, output_audio: 2.36 }, - limit: { context: 448, output: 448 }, - }, - "Qwen/Qwen3-30B-A3B-Instruct-2507-FP8": { - id: "Qwen/Qwen3-30B-A3B-Instruct-2507-FP8", - name: "Qwen3 30B 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2025-07-30", - last_updated: "2025-07-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.35, output: 1.42 }, - limit: { context: 64000, output: 64000 }, - }, - "Qwen/Qwen3-Embedding-8B": { - id: "Qwen/Qwen3-Embedding-8B", - name: "Qwen3 Embedding 8B", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2025-07-30", - last_updated: "2025-07-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.12, output: 0.12 }, - limit: { context: 40960, output: 40960 }, - }, - "Qwen/Qwen3-VL-30B-A3B-Instruct": { - id: "Qwen/Qwen3-VL-30B-A3B-Instruct", - name: "Qwen3 VL 30B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2025-07-30", - last_updated: "2025-07-30", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.24, output: 0.94 }, - limit: { context: 100000, output: 100000 }, - }, - "mistralai/Voxtral-Small-24B-2507": { - id: "mistralai/Voxtral-Small-24B-2507", - name: "Voxtral Small 24B", - family: "voxtral", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2025-03-01", - last_updated: "2025-03-01", - modalities: { input: ["audio", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.00236, output: 0.00236, output_audio: 2.36 }, - limit: { context: 32000, output: 32000 }, - }, - "mistralai/devstral-small-2-24b-instruct-2512": { - id: "mistralai/devstral-small-2-24b-instruct-2512", - name: "Devstral Small 2 24B Instruct 2512", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.12, output: 0.47 }, - limit: { context: 32768, output: 32768 }, - }, - "mistralai/Magistral-Small-2509": { - id: "mistralai/Magistral-Small-2509", - name: "Magistral Small 1.2 24B", - family: "magistral-small", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2025-06-01", - last_updated: "2025-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.59, output: 2.36 }, - limit: { context: 131072, output: 131072 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.24, output: 0.94 }, - limit: { context: 65536, output: 65536 }, - }, - "openai/whisper-large-v3": { - id: "openai/whisper-large-v3", - name: "Whisper 3 Large", - family: "whisper", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2024-10-01", - last_updated: "2024-10-01", - modalities: { input: ["audio"], output: ["text"] }, - open_weights: true, - cost: { input: 0.00236, output: 0.00236, output_audio: 2.36 }, - limit: { context: 448, output: 4096 }, - }, - }, - }, - zai: { - id: "zai", - env: ["ZHIPU_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.z.ai/api/paas/v4", - name: "Z.AI", - doc: "https://docs.z.ai/guides/overview/pricing", - models: { - "glm-5": { - id: "glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2, cache_read: 0.2, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "glm-4.5-air": { - id: "glm-4.5-air", - name: "GLM-4.5-Air", - family: "glm-air", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 1.1, cache_read: 0.03, cache_write: 0 }, - limit: { context: 131072, output: 98304 }, - }, - "glm-4.5": { - id: "glm-4.5", - name: "GLM-4.5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.11, cache_write: 0 }, - limit: { context: 131072, output: 98304 }, - }, - "glm-4.5-flash": { - id: "glm-4.5-flash", - name: "GLM-4.5-Flash", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 131072, output: 98304 }, - }, - "glm-4.7-flash": { - id: "glm-4.7-flash", - name: "GLM-4.7-Flash", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 200000, output: 131072 }, - }, - "glm-4.6": { - id: "glm-4.6", - name: "GLM-4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.11, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "glm-4.7": { - id: "glm-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.11, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "glm-4.5v": { - id: "glm-4.5v", - name: "GLM-4.5V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-08-11", - last_updated: "2025-08-11", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 1.8 }, - limit: { context: 64000, output: 16384 }, - }, - "glm-4.6v": { - id: "glm-4.6v", - name: "GLM-4.6V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 128000, output: 32768 }, - }, - }, - }, - "alibaba-coding-plan": { - id: "alibaba-coding-plan", - env: ["ALIBABA_CODING_PLAN_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://coding-intl.dashscope.aliyuncs.com/v1", - name: "Alibaba Coding Plan", - doc: "https://www.alibabacloud.com/help/en/model-studio/coding-plan", - models: { - "glm-5": { - id: "glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 202752, output: 16384 }, - }, - "MiniMax-M2.5": { - id: "MiniMax-M2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 196608, input: 196601, output: 24576 }, - }, - "qwen3-coder-next": { - id: "qwen3-coder-next", - name: "Qwen3 Coder Next", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-03", - last_updated: "2026-02-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 262144, output: 65536 }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 262144, output: 32768 }, - }, - "qwen3-max-2026-01-23": { - id: "qwen3-max-2026-01-23", - name: "Qwen3 Max", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-01-23", - last_updated: "2026-01-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 262144, output: 32768 }, - }, - "glm-4.7": { - id: "glm-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 202752, output: 16384 }, - }, - "qwen3.5-plus": { - id: "qwen3.5-plus", - name: "Qwen3.5 Plus", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-16", - last_updated: "2026-02-16", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 1000000, output: 65536 }, - }, - "qwen3-coder-plus": { - id: "qwen3-coder-plus", - name: "Qwen3 Coder Plus", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 1000000, output: 65536 }, - }, - }, - }, - zenmux: { - id: "zenmux", - env: ["ZENMUX_API_KEY"], - npm: "@ai-sdk/anthropic", - api: "https://zenmux.ai/api/anthropic/v1", - name: "ZenMux", - doc: "https://docs.zenmux.ai", - models: { - "xiaomi/mimo-v2-flash-free": { - id: "xiaomi/mimo-v2-flash-free", - name: "MiMo-V2-Flash Free", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 262000, output: 64000 }, - }, - "xiaomi/mimo-v2-flash": { - id: "xiaomi/mimo-v2-flash", - name: "MiMo-V2-Flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.3, cache_read: 0.01 }, - limit: { context: 262000, output: 64000 }, - }, - "kuaishou/kat-coder-pro-v1-free": { - id: "kuaishou/kat-coder-pro-v1-free", - name: "KAT-Coder-Pro-V1 Free", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-10-23", - last_updated: "2025-10-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 64000 }, - }, - "kuaishou/kat-coder-pro-v1": { - id: "kuaishou/kat-coder-pro-v1", - name: "KAT-Coder-Pro-V1", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-10-23", - last_updated: "2025-10-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2, cache_read: 0.06 }, - limit: { context: 256000, output: 64000 }, - }, - "stepfun/step-3.5-flash-free": { - id: "stepfun/step-3.5-flash-free", - name: "Step 3.5 Flash (Free)", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2026-02-02", - last_updated: "2026-02-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 64000 }, - }, - "stepfun/step-3.5-flash": { - id: "stepfun/step-3.5-flash", - name: "Step 3.5 Flash", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2026-02-02", - last_updated: "2026-02-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 256000, output: 64000 }, - }, - "stepfun/step-3": { - id: "stepfun/step-3", - name: "Step-3", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-07-31", - last_updated: "2025-07-31", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.21, output: 0.57 }, - limit: { context: 65536, output: 64000 }, - }, - "inclusionai/ling-1t": { - id: "inclusionai/ling-1t", - name: "Ling-1T", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-10-09", - last_updated: "2025-10-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.56, output: 2.24, cache_read: 0.11 }, - limit: { context: 128000, output: 64000 }, - }, - "inclusionai/ring-1t": { - id: "inclusionai/ring-1t", - name: "Ring-1T", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-10-12", - last_updated: "2025-10-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.56, output: 2.24, cache_read: 0.11 }, - limit: { context: 128000, output: 64000 }, - }, - "volcengine/doubao-seed-1.8": { - id: "volcengine/doubao-seed-1.8", - name: "Doubao-Seed-1.8", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-12-18", - last_updated: "2025-12-18", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.11, output: 0.28, cache_read: 0.02, cache_write: 0.0024 }, - limit: { context: 256000, output: 64000 }, - }, - "volcengine/doubao-seed-2.0-pro": { - id: "volcengine/doubao-seed-2.0-pro", - name: "Doubao-Seed-2.0-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2026-02-14", - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.45, output: 2.24, cache_read: 0.09, cache_write: 0.0024 }, - limit: { context: 256000, output: 64000 }, - }, - "volcengine/doubao-seed-2.0-mini": { - id: "volcengine/doubao-seed-2.0-mini", - name: "Doubao-Seed-2.0-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2026-02-14", - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.03, output: 0.28, cache_read: 0.01, cache_write: 0.0024 }, - limit: { context: 256000, output: 64000 }, - }, - "volcengine/doubao-seed-code": { - id: "volcengine/doubao-seed-code", - name: "Doubao-Seed-Code", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-11-11", - last_updated: "2025-11-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.17, output: 1.12, cache_read: 0.03 }, - limit: { context: 256000, output: 64000 }, - }, - "volcengine/doubao-seed-2.0-lite": { - id: "volcengine/doubao-seed-2.0-lite", - name: "Doubao-Seed-2.0-lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2026-02-14", - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09, output: 0.51, cache_read: 0.02, cache_write: 0.0024 }, - limit: { context: 256000, output: 64000 }, - }, - "deepseek/deepseek-v3.2": { - id: "deepseek/deepseek-v3.2", - name: "DeepSeek V3.2", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-12-05", - last_updated: "2025-12-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.28, output: 0.43 }, - limit: { context: 128000, output: 64000 }, - }, - "deepseek/deepseek-chat": { - id: "deepseek/deepseek-chat", - name: "DeepSeek-V3.2 (Non-thinking Mode)", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.28, output: 0.42, cache_read: 0.03 }, - limit: { context: 128000, output: 64000 }, - }, - "deepseek/deepseek-v3.2-exp": { - id: "deepseek/deepseek-v3.2-exp", - name: "DeepSeek-V3.2-Exp", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.22, output: 0.33 }, - limit: { context: 163000, output: 64000 }, - }, - "moonshotai/kimi-k2-0905": { - id: "moonshotai/kimi-k2-0905", - name: "Kimi K2 0905", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-09-04", - last_updated: "2025-09-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 2.5, cache_read: 0.15 }, - limit: { context: 262000, output: 64000 }, - }, - "moonshotai/kimi-k2.5": { - id: "moonshotai/kimi-k2.5", - name: "Kimi K2.5", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01-01", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.58, output: 3.02, cache_read: 0.1 }, - limit: { context: 262000, output: 64000 }, - }, - "moonshotai/kimi-k2-thinking": { - id: "moonshotai/kimi-k2-thinking", - name: "Kimi K2 Thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 2.5, cache_read: 0.15 }, - limit: { context: 262000, output: 64000 }, - }, - "moonshotai/kimi-k2-thinking-turbo": { - id: "moonshotai/kimi-k2-thinking-turbo", - name: "Kimi K2 Thinking Turbo", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.15, output: 8, cache_read: 0.15 }, - limit: { context: 262000, output: 64000 }, - }, - "baidu/ernie-5.0-thinking-preview": { - id: "baidu/ernie-5.0-thinking-preview", - name: "ERNIE 5.0", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2026-01-22", - last_updated: "2026-01-22", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.84, output: 3.37 }, - limit: { context: 128000, output: 64000 }, - }, - "google/gemini-2.5-flash": { - id: "google/gemini-2.5-flash", - name: "Gemini 2.5 Flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["pdf", "image", "text", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.07, cache_write: 1 }, - limit: { context: 1048000, output: 64000 }, - }, - "google/gemini-3-flash-preview": { - id: "google/gemini-3-flash-preview", - name: "Gemini 3 Flash Preview", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "pdf", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 3, cache_read: 0.05, cache_write: 1 }, - limit: { context: 1048000, output: 64000 }, - }, - "google/gemini-2.5-flash-lite": { - id: "google/gemini-2.5-flash-lite", - name: "Gemini 2.5 Flash Lite", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-07-22", - last_updated: "2025-07-22", - modalities: { input: ["pdf", "image", "text", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.03, cache_write: 1 }, - limit: { context: 1048000, output: 64000 }, - }, - "google/gemini-3.1-pro-preview": { - id: "google/gemini-3.1-pro-preview", - name: "Gemini 3.1 Pro Preview", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2026-02-19", - release_date: "2026-02-19", - last_updated: "2026-02-19", - modalities: { input: ["text", "image", "pdf", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, cache_write: 4.5 }, - limit: { context: 1048000, output: 64000 }, - }, - "google/gemini-3-pro-preview": { - id: "google/gemini-3-pro-preview", - name: "Gemini 3 Pro Preview", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "pdf", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, cache_write: 4.5 }, - limit: { context: 1048000, output: 64000 }, - }, - "google/gemini-2.5-pro": { - id: "google/gemini-2.5-pro", - name: "Gemini 2.5 Pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["pdf", "image", "text", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31, cache_write: 4.5 }, - limit: { context: 1048000, output: 64000 }, - }, - "z-ai/glm-5": { - id: "z-ai/glm-5", - name: "GLM 5", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01-01", - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.58, output: 2.6, cache_read: 0.14 }, - limit: { context: 200000, output: 128000 }, - }, - "z-ai/glm-4.7-flashx": { - id: "z-ai/glm-4.7-flashx", - name: "GLM 4.7 FlashX", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01-01", - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.42, cache_read: 0.01 }, - limit: { context: 200000, output: 64000 }, - }, - "z-ai/glm-4.5-air": { - id: "z-ai/glm-4.5-air", - name: "GLM 4.5 Air", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-07-25", - last_updated: "2025-07-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.11, output: 0.56, cache_read: 0.02 }, - limit: { context: 128000, output: 64000 }, - }, - "z-ai/glm-4.5": { - id: "z-ai/glm-4.5", - name: "GLM 4.5", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-07-25", - last_updated: "2025-07-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.35, output: 1.54, cache_read: 0.07 }, - limit: { context: 128000, output: 64000 }, - }, - "z-ai/glm-4.6v-flash-free": { - id: "z-ai/glm-4.6v-flash-free", - name: "GLM 4.6V Flash (Free)", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 200000, output: 64000 }, - }, - "z-ai/glm-4.6": { - id: "z-ai/glm-4.6", - name: "GLM 4.6", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.35, output: 1.54, cache_read: 0.07 }, - limit: { context: 200000, output: 64000 }, - }, - "z-ai/glm-4.7": { - id: "z-ai/glm-4.7", - name: "GLM 4.7", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.28, output: 1.14, cache_read: 0.06 }, - limit: { context: 200000, output: 64000 }, - }, - "z-ai/glm-4.7-flash-free": { - id: "z-ai/glm-4.7-flash-free", - name: "GLM 4.7 Flash (Free)", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 200000, output: 64000 }, - }, - "z-ai/glm-4.6v-flash": { - id: "z-ai/glm-4.6v-flash", - name: "GLM 4.6V FlashX", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0.21, cache_read: 0.0043 }, - limit: { context: 200000, output: 64000 }, - }, - "z-ai/glm-4.6v": { - id: "z-ai/glm-4.6v", - name: "GLM 4.6V", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.42, cache_read: 0.03 }, - limit: { context: 200000, output: 64000 }, - }, - "qwen/qwen3-max": { - id: "qwen/qwen3-max", - name: "Qwen3-Max-Thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2026-01-23", - last_updated: "2026-01-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.2, output: 6 }, - limit: { context: 256000, output: 64000 }, - }, - "qwen/qwen3-coder-plus": { - id: "qwen/qwen3-coder-plus", - name: "Qwen3-Coder-Plus", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 1000000, output: 64000 }, - }, - "x-ai/grok-code-fast-1": { - id: "x-ai/grok-code-fast-1", - name: "Grok Code Fast 1", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-08-26", - last_updated: "2025-08-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.5, cache_read: 0.02 }, - limit: { context: 256000, output: 64000 }, - }, - "x-ai/grok-4-fast": { - id: "x-ai/grok-4-fast", - name: "Grok 4 Fast", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-09-19", - last_updated: "2025-09-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 64000 }, - }, - "x-ai/grok-4": { - id: "x-ai/grok-4", - name: "Grok 4", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75 }, - limit: { context: 256000, output: 64000 }, - }, - "x-ai/grok-4.1-fast-non-reasoning": { - id: "x-ai/grok-4.1-fast-non-reasoning", - name: "Grok 4.1 Fast Non Reasoning", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-11-20", - last_updated: "2025-11-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 64000 }, - }, - "x-ai/grok-4.1-fast": { - id: "x-ai/grok-4.1-fast", - name: "Grok 4.1 Fast", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-11-20", - last_updated: "2025-11-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 64000 }, - }, - "openai/gpt-5-codex": { - id: "openai/gpt-5-codex", - name: "GPT-5 Codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-09-23", - last_updated: "2025-09-23", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.12 }, - limit: { context: 400000, output: 64000 }, - }, - "openai/gpt-5.2-codex": { - id: "openai/gpt-5.2-codex", - name: "GPT-5.2-Codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-01-01", - release_date: "2026-01-15", - last_updated: "2026-01-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.17 }, - limit: { context: 400000, output: 64000 }, - }, - "openai/gpt-5.1": { - id: "openai/gpt-5.1", - name: "GPT-5.1", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["image", "text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.12 }, - limit: { context: 400000, output: 64000 }, - }, - "openai/gpt-5.1-chat": { - id: "openai/gpt-5.1-chat", - name: "GPT-5.1 Chat", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["pdf", "image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.12 }, - limit: { context: 128000, output: 64000 }, - }, - "openai/gpt-5.1-codex-mini": { - id: "openai/gpt-5.1-codex-mini", - name: "GPT-5.1-Codex-Mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.03 }, - limit: { context: 400000, output: 64000 }, - }, - "openai/gpt-5.2": { - id: "openai/gpt-5.2", - name: "GPT-5.2", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-01-01", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["image", "text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.17 }, - limit: { context: 400000, output: 64000 }, - }, - "openai/gpt-5": { - id: "openai/gpt-5", - name: "GPT-5", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.12 }, - limit: { context: 400000, output: 64000 }, - }, - "openai/gpt-5.1-codex": { - id: "openai/gpt-5.1-codex", - name: "GPT-5.1-Codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.12 }, - limit: { context: 400000, output: 64000 }, - }, - "openai/gpt-5.2-pro": { - id: "openai/gpt-5.2-pro", - name: "GPT-5.2-Pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 21, output: 168 }, - limit: { context: 400000, output: 128000 }, - }, - "minimax/minimax-m2.5-lightning": { - id: "minimax/minimax-m2.5-lightning", - name: "MiniMax M2.5 highspeed", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01-01", - release_date: "2026-02-13", - last_updated: "2026-02-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 4.8, cache_read: 0.06, cache_write: 0.75 }, - limit: { context: 204800, output: 131072 }, - }, - "minimax/minimax-m2.1": { - id: "minimax/minimax-m2.1", - name: "MiniMax M2.1", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2, cache_read: 0.03, cache_write: 0.38 }, - limit: { context: 204000, output: 64000 }, - }, - "minimax/minimax-m2": { - id: "minimax/minimax-m2", - name: "MiniMax M2", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-10-27", - last_updated: "2025-10-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2, cache_read: 0.03, cache_write: 0.38 }, - limit: { context: 204000, output: 64000 }, - }, - "minimax/minimax-m2.5": { - id: "minimax/minimax-m2.5", - name: "MiniMax M2.5", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01-01", - release_date: "2026-02-13", - last_updated: "2026-02-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2, cache_read: 0.03, cache_write: 0.375 }, - limit: { context: 204800, output: 131072 }, - }, - "anthropic/claude-3.5-sonnet": { - id: "anthropic/claude-3.5-sonnet", - name: "Claude 3.5 Sonnet (Retiring Soon)", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-3.7-sonnet": { - id: "anthropic/claude-3.7-sonnet", - name: "Claude 3.7 Sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-02-24", - last_updated: "2025-02-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-opus-4.1": { - id: "anthropic/claude-opus-4.1", - name: "Claude Opus 4.1", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["image", "text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-sonnet-4.6": { - id: "anthropic/claude-sonnet-4.6", - name: "Claude Sonnet 4.6", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2026-02-18", - last_updated: "2026-02-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 1000000, output: 64000 }, - }, - "anthropic/claude-haiku-4.5": { - id: "anthropic/claude-haiku-4.5", - name: "Claude Haiku 4.5", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-3.5-haiku": { - id: "anthropic/claude-3.5-haiku", - name: "Claude 3.5 Haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2024-11-04", - last_updated: "2024-11-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-opus-4.5": { - id: "anthropic/claude-opus-4.5", - name: "Claude Opus 4.5", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-11-24", - last_updated: "2025-11-24", - modalities: { input: ["pdf", "image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-opus-4": { - id: "anthropic/claude-opus-4", - name: "Claude Opus 4", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["image", "text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-sonnet-4": { - id: "anthropic/claude-sonnet-4", - name: "Claude Sonnet 4", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["image", "text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 1000000, output: 64000 }, - }, - "anthropic/claude-sonnet-4.5": { - id: "anthropic/claude-sonnet-4.5", - name: "Claude Sonnet 4.5", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 1000000, output: 64000 }, - }, - "anthropic/claude-opus-4.6": { - id: "anthropic/claude-opus-4.6", - name: "Claude Opus 4.6", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2026-02-06", - last_updated: "2026-02-06", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 1000000, output: 128000 }, - }, - }, - }, - "io-net": { - id: "io-net", - env: ["IOINTELLIGENCE_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.intelligence.io.solutions/api/v1", - name: "IO.NET", - doc: "https://io.net/docs/guides/intelligence/io-intelligence", - models: { - "zai-org/GLM-4.6": { - id: "zai-org/GLM-4.6", - name: "GLM 4.6", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-11-15", - last_updated: "2024-11-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.75, cache_read: 0.2, cache_write: 0.8 }, - limit: { context: 200000, output: 4096 }, - }, - "deepseek-ai/DeepSeek-R1-0528": { - id: "deepseek-ai/DeepSeek-R1-0528", - name: "DeepSeek R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-01-20", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 8.75, cache_read: 1, cache_write: 4 }, - limit: { context: 128000, output: 4096 }, - }, - "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar": { - id: "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar", - name: "Qwen 3 Coder 480B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-15", - last_updated: "2025-01-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.22, output: 0.95, cache_read: 0.11, cache_write: 0.44 }, - limit: { context: 106000, output: 4096 }, - }, - "moonshotai/Kimi-K2-Instruct-0905": { - id: "moonshotai/Kimi-K2-Instruct-0905", - name: "Kimi K2 Instruct", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-09-05", - last_updated: "2024-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.39, output: 1.9, cache_read: 0.195, cache_write: 0.78 }, - limit: { context: 32768, output: 4096 }, - }, - "moonshotai/Kimi-K2-Thinking": { - id: "moonshotai/Kimi-K2-Thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-11-01", - last_updated: "2024-11-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.55, output: 2.25, cache_read: 0.275, cache_write: 1.1 }, - limit: { context: 32768, output: 4096 }, - }, - "meta-llama/Llama-3.2-90B-Vision-Instruct": { - id: "meta-llama/Llama-3.2-90B-Vision-Instruct", - name: "Llama 3.2 90B Vision Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.35, output: 0.4, cache_read: 0.175, cache_write: 0.7 }, - limit: { context: 16000, output: 4096 }, - }, - "meta-llama/Llama-3.3-70B-Instruct": { - id: "meta-llama/Llama-3.3-70B-Instruct", - name: "Llama 3.3 70B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.38, cache_read: 0.065, cache_write: 0.26 }, - limit: { context: 128000, output: 4096 }, - }, - "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": { - id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", - name: "Llama 4 Maverick 17B 128E Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-15", - last_updated: "2025-01-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6, cache_read: 0.075, cache_write: 0.3 }, - limit: { context: 430000, output: 4096 }, - }, - "Qwen/Qwen3-Next-80B-A3B-Instruct": { - id: "Qwen/Qwen3-Next-80B-A3B-Instruct", - name: "Qwen 3 Next 80B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-10", - last_updated: "2025-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.8, cache_read: 0.05, cache_write: 0.2 }, - limit: { context: 262144, output: 4096 }, - }, - "Qwen/Qwen3-235B-A22B-Thinking-2507": { - id: "Qwen/Qwen3-235B-A22B-Thinking-2507", - name: "Qwen 3 235B Thinking", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-07-01", - last_updated: "2025-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.11, output: 0.6, cache_read: 0.055, cache_write: 0.22 }, - limit: { context: 262144, output: 4096 }, - }, - "Qwen/Qwen2.5-VL-32B-Instruct": { - id: "Qwen/Qwen2.5-VL-32B-Instruct", - name: "Qwen 2.5 VL 32B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2024-11-01", - last_updated: "2024-11-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.22, cache_read: 0.025, cache_write: 0.1 }, - limit: { context: 32000, output: 4096 }, - }, - "mistralai/Mistral-Nemo-Instruct-2407": { - id: "mistralai/Mistral-Nemo-Instruct-2407", - name: "Mistral Nemo Instruct 2407", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-05", - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.04, cache_read: 0.01, cache_write: 0.04 }, - limit: { context: 128000, output: 4096 }, - }, - "mistralai/Magistral-Small-2506": { - id: "mistralai/Magistral-Small-2506", - name: "Magistral Small 2506", - family: "magistral-small", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-01", - last_updated: "2025-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.5, cache_read: 0.25, cache_write: 1 }, - limit: { context: 128000, output: 4096 }, - }, - "mistralai/Mistral-Large-Instruct-2411": { - id: "mistralai/Mistral-Large-Instruct-2411", - name: "Mistral Large Instruct 2411", - family: "mistral-large", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-11-01", - last_updated: "2024-11-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6, cache_read: 1, cache_write: 4 }, - limit: { context: 128000, output: 4096 }, - }, - "mistralai/Devstral-Small-2505": { - id: "mistralai/Devstral-Small-2505", - name: "Devstral Small 2505", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-05-01", - last_updated: "2025-05-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.22, cache_read: 0.025, cache_write: 0.1 }, - limit: { context: 128000, output: 4096 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT-OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.4, cache_read: 0.02, cache_write: 0.08 }, - limit: { context: 131072, output: 4096 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "GPT-OSS 20B", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.14, cache_read: 0.015, cache_write: 0.06 }, - limit: { context: 64000, output: 4096 }, - }, - }, - }, - nvidia: { - id: "nvidia", - env: ["NVIDIA_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://integrate.api.nvidia.com/v1", - name: "Nvidia", - doc: "https://docs.api.nvidia.com/nim/", - models: { - "nvidia/llama-3.1-nemotron-70b-instruct": { - id: "nvidia/llama-3.1-nemotron-70b-instruct", - name: "Llama 3.1 Nemotron 70b Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-10-12", - last_updated: "2024-10-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "nvidia/llama-3.1-nemotron-ultra-253b-v1": { - id: "nvidia/llama-3.1-nemotron-ultra-253b-v1", - name: "Llama-3.1-Nemotron-Ultra-253B-v1", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-07-01", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 8192 }, - }, - "nvidia/llama-3.1-nemotron-51b-instruct": { - id: "nvidia/llama-3.1-nemotron-51b-instruct", - name: "Llama 3.1 Nemotron 51b Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-09-22", - last_updated: "2024-09-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "nvidia/parakeet-tdt-0.6b-v2": { - id: "nvidia/parakeet-tdt-0.6b-v2", - name: "Parakeet TDT 0.6B v2", - family: "parakeet", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2024-01", - release_date: "2024-01-01", - last_updated: "2025-09-05", - modalities: { input: ["audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 0, output: 4096 }, - }, - "nvidia/nvidia-nemotron-nano-9b-v2": { - id: "nvidia/nvidia-nemotron-nano-9b-v2", - name: "nvidia-nemotron-nano-9b-v2", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2025-08-18", - last_updated: "2025-08-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 131072 }, - }, - "nvidia/llama-embed-nemotron-8b": { - id: "nvidia/llama-embed-nemotron-8b", - name: "Llama Embed Nemotron 8B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2025-03", - release_date: "2025-03-18", - last_updated: "2025-03-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 2048 }, - }, - "nvidia/llama-3.3-nemotron-super-49b-v1.5": { - id: "nvidia/llama-3.3-nemotron-super-49b-v1.5", - name: "Llama 3.3 Nemotron Super 49b V1.5", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-03-16", - last_updated: "2025-03-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "nvidia/llama-3.3-nemotron-super-49b-v1": { - id: "nvidia/llama-3.3-nemotron-super-49b-v1", - name: "Llama 3.3 Nemotron Super 49b V1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-03-16", - last_updated: "2025-03-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "nvidia/llama3-chatqa-1.5-70b": { - id: "nvidia/llama3-chatqa-1.5-70b", - name: "Llama3 Chatqa 1.5 70b", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-04-28", - last_updated: "2024-04-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "nvidia/cosmos-nemotron-34b": { - id: "nvidia/cosmos-nemotron-34b", - name: "Cosmos Nemotron 34B", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-01", - release_date: "2024-01-01", - last_updated: "2025-09-05", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 8192 }, - }, - "nvidia/nemoretriever-ocr-v1": { - id: "nvidia/nemoretriever-ocr-v1", - name: "NeMo Retriever OCR v1", - family: "nemoretriever", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2024-01", - release_date: "2024-01-01", - last_updated: "2025-09-05", - modalities: { input: ["image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 0, output: 4096 }, - }, - "nvidia/nemotron-4-340b-instruct": { - id: "nvidia/nemotron-4-340b-instruct", - name: "Nemotron 4 340b Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-06-13", - last_updated: "2024-06-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "nvidia/nemotron-3-nano-30b-a3b": { - id: "nvidia/nemotron-3-nano-30b-a3b", - name: "nemotron-3-nano-30b-a3b", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2024-12", - last_updated: "2024-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 131072 }, - }, - "microsoft/phi-3-small-128k-instruct": { - id: "microsoft/phi-3-small-128k-instruct", - name: "Phi 3 Small 128k Instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-05-07", - last_updated: "2024-05-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-3-medium-128k-instruct": { - id: "microsoft/phi-3-medium-128k-instruct", - name: "Phi 3 Medium 128k Instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-05-07", - last_updated: "2024-05-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-3.5-moe-instruct": { - id: "microsoft/phi-3.5-moe-instruct", - name: "Phi 3.5 Moe Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-08-17", - last_updated: "2024-08-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-3-vision-128k-instruct": { - id: "microsoft/phi-3-vision-128k-instruct", - name: "Phi 3 Vision 128k Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-05-19", - last_updated: "2024-05-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-4-mini-instruct": { - id: "microsoft/phi-4-mini-instruct", - name: "Phi-4-Mini", - family: "phi", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2024-12-01", - last_updated: "2025-09-05", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 8192 }, - }, - "microsoft/phi-3.5-vision-instruct": { - id: "microsoft/phi-3.5-vision-instruct", - name: "Phi 3.5 Vision Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-08-16", - last_updated: "2024-08-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-3-medium-4k-instruct": { - id: "microsoft/phi-3-medium-4k-instruct", - name: "Phi 3 Medium 4k Instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-05-07", - last_updated: "2024-05-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 4000, output: 4096 }, - }, - "microsoft/phi-3-small-8k-instruct": { - id: "microsoft/phi-3-small-8k-instruct", - name: "Phi 3 Small 8k Instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-05-07", - last_updated: "2024-05-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 8000, output: 4096 }, - }, - "minimaxai/minimax-m2.1": { - id: "minimaxai/minimax-m2.1", - name: "MiniMax-M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "minimaxai/minimax-m2.5": { - id: "minimaxai/minimax-m2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "deepseek-ai/deepseek-v3.1": { - id: "deepseek-ai/deepseek-v3.1", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-08-20", - last_updated: "2025-08-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "deepseek-ai/deepseek-r1-0528": { - id: "deepseek-ai/deepseek-r1-0528", - name: "Deepseek R1 0528", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "deepseek-ai/deepseek-r1": { - id: "deepseek-ai/deepseek-r1", - name: "Deepseek R1", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "deepseek-ai/deepseek-v3.1-terminus": { - id: "deepseek-ai/deepseek-v3.1-terminus", - name: "DeepSeek V3.1 Terminus", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-22", - last_updated: "2025-09-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "deepseek-ai/deepseek-coder-6.7b-instruct": { - id: "deepseek-ai/deepseek-coder-6.7b-instruct", - name: "Deepseek Coder 6.7b Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2023-10-29", - last_updated: "2023-10-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "deepseek-ai/deepseek-v3.2": { - id: "deepseek-ai/deepseek-v3.2", - name: "DeepSeek V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 163840, output: 65536 }, - }, - "moonshotai/kimi-k2-instruct": { - id: "moonshotai/kimi-k2-instruct", - name: "Kimi K2 Instruct", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-01", - release_date: "2025-01-01", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "moonshotai/kimi-k2-instruct-0905": { - id: "moonshotai/kimi-k2-instruct-0905", - name: "Kimi K2 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 262144 }, - }, - "moonshotai/kimi-k2.5": { - id: "moonshotai/kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-07", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 262144 }, - }, - "moonshotai/kimi-k2-thinking": { - id: "moonshotai/kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-11", - last_updated: "2025-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 262144, output: 262144 }, - }, - "google/codegemma-7b": { - id: "google/codegemma-7b", - name: "Codegemma 7b", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2024-03-21", - last_updated: "2024-03-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "google/gemma-2-2b-it": { - id: "google/gemma-2-2b-it", - name: "Gemma 2 2b It", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-07-16", - last_updated: "2024-07-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "google/gemma-3-1b-it": { - id: "google/gemma-3-1b-it", - name: "Gemma 3 1b It", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-03-10", - last_updated: "2025-03-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "google/gemma-2-27b-it": { - id: "google/gemma-2-27b-it", - name: "Gemma 2 27b It", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-06-24", - last_updated: "2024-06-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "google/gemma-3n-e2b-it": { - id: "google/gemma-3n-e2b-it", - name: "Gemma 3n E2b It", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-06", - release_date: "2025-06-12", - last_updated: "2025-06-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "google/codegemma-1.1-7b": { - id: "google/codegemma-1.1-7b", - name: "Codegemma 1.1 7b", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2024-04-30", - last_updated: "2024-04-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "google/gemma-3n-e4b-it": { - id: "google/gemma-3n-e4b-it", - name: "Gemma 3n E4b It", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-06", - release_date: "2025-06-03", - last_updated: "2025-06-03", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "google/gemma-3-12b-it": { - id: "google/gemma-3-12b-it", - name: "Gemma 3 12b It", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-03-01", - last_updated: "2025-03-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "google/gemma-3-27b-it": { - id: "google/gemma-3-27b-it", - name: "Gemma-3-27B-IT", - family: "gemma", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2024-12-01", - last_updated: "2025-09-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 8192 }, - }, - "z-ai/glm4.7": { - id: "z-ai/glm4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "z-ai/glm5": { - id: "z-ai/glm5", - name: "GLM5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 202752, output: 131000 }, - }, - "stepfun-ai/step-3.5-flash": { - id: "stepfun-ai/step-3.5-flash", - name: "Step 3.5 Flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-02", - last_updated: "2026-02-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 16384 }, - }, - "qwen/qwen3-next-80b-a3b-thinking": { - id: "qwen/qwen3-next-80b-a3b-thinking", - name: "Qwen3-Next-80B-A3B-Thinking", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2024-12-01", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 16384 }, - }, - "qwen/qwen3-coder-480b-a35b-instruct": { - id: "qwen/qwen3-coder-480b-a35b-instruct", - name: "Qwen3 Coder 480B A35B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 66536 }, - }, - "qwen/qwq-32b": { - id: "qwen/qwq-32b", - name: "Qwq 32b", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-03-05", - last_updated: "2025-03-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "qwen/qwen2.5-coder-7b-instruct": { - id: "qwen/qwen2.5-coder-7b-instruct", - name: "Qwen2.5 Coder 7b Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-09-17", - last_updated: "2024-09-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "qwen/qwen3.5-397b-a17b": { - id: "qwen/qwen3.5-397b-a17b", - name: "Qwen3.5-397B-A17B", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2026-01", - release_date: "2026-02-16", - last_updated: "2026-02-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 8192 }, - }, - "qwen/qwen2.5-coder-32b-instruct": { - id: "qwen/qwen2.5-coder-32b-instruct", - name: "Qwen2.5 Coder 32b Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-11-06", - last_updated: "2024-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "qwen/qwen3-235b-a22b": { - id: "qwen/qwen3-235b-a22b", - name: "Qwen3-235B-A22B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2024-12-01", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen/qwen3-next-80b-a3b-instruct": { - id: "qwen/qwen3-next-80b-a3b-instruct", - name: "Qwen3-Next-80B-A3B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2024-12-01", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 16384 }, - }, - "meta/llama-3.1-70b-instruct": { - id: "meta/llama-3.1-70b-instruct", - name: "Llama 3.1 70b Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-07-16", - last_updated: "2024-07-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "meta/llama-3.3-70b-instruct": { - id: "meta/llama-3.3-70b-instruct", - name: "Llama 3.3 70b Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-11-26", - last_updated: "2024-11-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "meta/llama-4-scout-17b-16e-instruct": { - id: "meta/llama-4-scout-17b-16e-instruct", - name: "Llama 4 Scout 17b 16e Instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-02", - release_date: "2025-04-02", - last_updated: "2025-04-02", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "meta/llama-3.2-11b-vision-instruct": { - id: "meta/llama-3.2-11b-vision-instruct", - name: "Llama 3.2 11b Vision Instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-18", - last_updated: "2024-09-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "meta/llama3-8b-instruct": { - id: "meta/llama3-8b-instruct", - name: "Llama3 8b Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-04-17", - last_updated: "2024-04-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "meta/codellama-70b": { - id: "meta/codellama-70b", - name: "Codellama 70b", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2024-01-29", - last_updated: "2024-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "meta/llama-3.2-1b-instruct": { - id: "meta/llama-3.2-1b-instruct", - name: "Llama 3.2 1b Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-18", - last_updated: "2024-09-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "meta/llama-3.1-405b-instruct": { - id: "meta/llama-3.1-405b-instruct", - name: "Llama 3.1 405b Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-07-16", - last_updated: "2024-07-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "meta/llama3-70b-instruct": { - id: "meta/llama3-70b-instruct", - name: "Llama3 70b Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-04-17", - last_updated: "2024-04-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "meta/llama-4-maverick-17b-128e-instruct": { - id: "meta/llama-4-maverick-17b-128e-instruct", - name: "Llama 4 Maverick 17b 128e Instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-02", - release_date: "2025-04-01", - last_updated: "2025-04-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "mistralai/mistral-large-3-675b-instruct-2512": { - id: "mistralai/mistral-large-3-675b-instruct-2512", - name: "Mistral Large 3 675B Instruct 2512", - family: "mistral-large", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-02", - last_updated: "2025-12-02", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 262144 }, - }, - "mistralai/mamba-codestral-7b-v0.1": { - id: "mistralai/mamba-codestral-7b-v0.1", - name: "Mamba Codestral 7b V0.1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2024-07-16", - last_updated: "2024-07-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "mistralai/codestral-22b-instruct-v0.1": { - id: "mistralai/codestral-22b-instruct-v0.1", - name: "Codestral 22b Instruct V0.1", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-05-29", - last_updated: "2024-05-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "mistralai/mistral-large-2-instruct": { - id: "mistralai/mistral-large-2-instruct", - name: "Mistral Large 2 Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-07-24", - last_updated: "2024-07-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "mistralai/ministral-14b-instruct-2512": { - id: "mistralai/ministral-14b-instruct-2512", - name: "Ministral 3 14B Instruct 2512", - family: "ministral", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-12", - release_date: "2025-12-01", - last_updated: "2025-12-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 262144 }, - }, - "mistralai/mistral-small-3.1-24b-instruct-2503": { - id: "mistralai/mistral-small-3.1-24b-instruct-2503", - name: "Mistral Small 3.1 24b Instruct 2503", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-03-11", - last_updated: "2025-03-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "mistralai/devstral-2-123b-instruct-2512": { - id: "mistralai/devstral-2-123b-instruct-2512", - name: "Devstral-2-123B-Instruct-2512", - family: "devstral", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-12", - release_date: "2025-12-08", - last_updated: "2025-12-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 262144 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT-OSS-120B", - family: "gpt-oss", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-08", - release_date: "2025-08-04", - last_updated: "2025-08-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "openai/whisper-large-v3": { - id: "openai/whisper-large-v3", - name: "Whisper Large v3", - family: "whisper", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2023-09", - release_date: "2023-09-01", - last_updated: "2025-09-05", - modalities: { input: ["audio"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 0, output: 4096 }, - }, - "black-forest-labs/flux.1-dev": { - id: "black-forest-labs/flux.1-dev", - name: "FLUX.1-dev", - family: "flux", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-08", - release_date: "2024-08-01", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 4096, output: 0 }, - }, - }, - }, - fastrouter: { - id: "fastrouter", - env: ["FASTROUTER_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://go.fastrouter.ai/api/v1", - name: "FastRouter", - doc: "https://fastrouter.ai/models", - models: { - "deepseek-ai/deepseek-r1-distill-llama-70b": { - id: "deepseek-ai/deepseek-r1-distill-llama-70b", - name: "DeepSeek R1 Distill Llama 70B", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2025-01-23", - last_updated: "2025-01-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.14 }, - limit: { context: 131072, output: 131072 }, - }, - "moonshotai/kimi-k2": { - id: "moonshotai/kimi-k2", - name: "Kimi K2", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-11", - last_updated: "2025-07-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.2 }, - limit: { context: 131072, output: 32768 }, - }, - "google/gemini-2.5-flash": { - id: "google/gemini-2.5-flash", - name: "Gemini 2.5 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.0375 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-2.5-pro": { - id: "google/gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31 }, - limit: { context: 1048576, output: 65536 }, - }, - "qwen/qwen3-coder": { - id: "qwen/qwen3-coder", - name: "Qwen3 Coder", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 262144, output: 66536 }, - }, - "x-ai/grok-4": { - id: "x-ai/grok-4", - name: "Grok 4", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75, cache_write: 15 }, - limit: { context: 256000, output: 64000 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 131072, output: 32768 }, - }, - "openai/gpt-4.1": { - id: "openai/gpt-4.1", - name: "GPT-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/gpt-5": { - id: "openai/gpt-5", - name: "GPT-5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10-01", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5-mini": { - id: "openai/gpt-5-mini", - name: "GPT-5 Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10-01", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.025 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "GPT OSS 20B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.2 }, - limit: { context: 131072, output: 65536 }, - }, - "openai/gpt-5-nano": { - id: "openai/gpt-5-nano", - name: "GPT-5 Nano", - family: "gpt-nano", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10-01", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4, cache_read: 0.005 }, - limit: { context: 400000, output: 128000 }, - }, - "anthropic/claude-opus-4.1": { - id: "anthropic/claude-opus-4.1", - name: "Claude Opus 4.1", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "anthropic/claude-sonnet-4": { - id: "anthropic/claude-sonnet-4", - name: "Claude Sonnet 4", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - }, - }, - iflowcn: { - id: "iflowcn", - env: ["IFLOW_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://apis.iflow.cn/v1", - name: "iFlow", - doc: "https://platform.iflow.cn/en/docs", - models: { - "kimi-k2": { - id: "kimi-k2", - name: "Kimi-K2", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 64000 }, - }, - "qwen3-max-preview": { - id: "qwen3-max-preview", - name: "Qwen3-Max-Preview", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 32000 }, - }, - "deepseek-v3": { - id: "deepseek-v3", - name: "DeepSeek-V3", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-26", - last_updated: "2024-12-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32000 }, - }, - "kimi-k2-0905": { - id: "kimi-k2-0905", - name: "Kimi-K2-0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 64000 }, - }, - "qwen3-235b-a22b-instruct": { - id: "qwen3-235b-a22b-instruct", - name: "Qwen3-235B-A22B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-01", - last_updated: "2025-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 64000 }, - }, - "glm-4.6": { - id: "glm-4.6", - name: "GLM-4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-01", - last_updated: "2025-11-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 200000, output: 128000 }, - }, - "deepseek-r1": { - id: "deepseek-r1", - name: "DeepSeek-R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32000 }, - }, - "qwen3-32b": { - id: "qwen3-32b", - name: "Qwen3-32B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32000 }, - }, - "deepseek-v3.2": { - id: "deepseek-v3.2", - name: "DeepSeek-V3.2-Exp", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 64000 }, - }, - "qwen3-235b": { - id: "qwen3-235b", - name: "Qwen3-235B-A22B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32000 }, - }, - "qwen3-vl-plus": { - id: "qwen3-vl-plus", - name: "Qwen3-VL-Plus", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 32000 }, - }, - "qwen3-235b-a22b-thinking-2507": { - id: "qwen3-235b-a22b-thinking-2507", - name: "Qwen3-235B-A22B-Thinking", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-01", - last_updated: "2025-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 64000 }, - }, - "qwen3-max": { - id: "qwen3-max", - name: "Qwen3-Max", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 32000 }, - }, - "qwen3-coder-plus": { - id: "qwen3-coder-plus", - name: "Qwen3-Coder-Plus", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-01", - last_updated: "2025-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 64000 }, - }, - }, - }, - modelscope: { - id: "modelscope", - env: ["MODELSCOPE_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api-inference.modelscope.cn/v1", - name: "ModelScope", - doc: "https://modelscope.cn/docs/model-service/API-Inference/intro", - models: { - "Qwen/Qwen3-30B-A3B-Instruct-2507": { - id: "Qwen/Qwen3-30B-A3B-Instruct-2507", - name: "Qwen3 30B A3B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-30", - last_updated: "2025-07-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 16384 }, - }, - "Qwen/Qwen3-235B-A22B-Thinking-2507": { - id: "Qwen/Qwen3-235B-A22B-Thinking-2507", - name: "Qwen3-235B-A22B-Thinking-2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-25", - last_updated: "2025-07-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 131072 }, - }, - "Qwen/Qwen3-30B-A3B-Thinking-2507": { - id: "Qwen/Qwen3-30B-A3B-Thinking-2507", - name: "Qwen3 30B A3B Thinking 2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-30", - last_updated: "2025-07-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 32768 }, - }, - "Qwen/Qwen3-Coder-30B-A3B-Instruct": { - id: "Qwen/Qwen3-Coder-30B-A3B-Instruct", - name: "Qwen3 Coder 30B A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-31", - last_updated: "2025-07-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 65536 }, - }, - "Qwen/Qwen3-235B-A22B-Instruct-2507": { - id: "Qwen/Qwen3-235B-A22B-Instruct-2507", - name: "Qwen3 235B A22B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-28", - last_updated: "2025-07-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 131072 }, - }, - "ZhipuAI/GLM-4.6": { - id: "ZhipuAI/GLM-4.6", - name: "GLM-4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 202752, output: 98304 }, - }, - "ZhipuAI/GLM-4.5": { - id: "ZhipuAI/GLM-4.5", - name: "GLM-4.5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 98304 }, - }, - }, - }, - llama: { - id: "llama", - env: ["LLAMA_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.llama.com/compat/v1/", - name: "Llama", - doc: "https://llama.developer.meta.com/docs/models", - models: { - "cerebras-llama-4-maverick-17b-128e-instruct": { - id: "cerebras-llama-4-maverick-17b-128e-instruct", - name: "Cerebras-Llama-4-Maverick-17B-128E-Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "llama-4-scout-17b-16e-instruct-fp8": { - id: "llama-4-scout-17b-16e-instruct-fp8", - name: "Llama-4-Scout-17B-16E-Instruct-FP8", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "llama-3.3-8b-instruct": { - id: "llama-3.3-8b-instruct", - name: "Llama-3.3-8B-Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "groq-llama-4-maverick-17b-128e-instruct": { - id: "groq-llama-4-maverick-17b-128e-instruct", - name: "Groq-Llama-4-Maverick-17B-128E-Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "llama-3.3-70b-instruct": { - id: "llama-3.3-70b-instruct", - name: "Llama-3.3-70B-Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "cerebras-llama-4-scout-17b-16e-instruct": { - id: "cerebras-llama-4-scout-17b-16e-instruct", - name: "Cerebras-Llama-4-Scout-17B-16E-Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "llama-4-maverick-17b-128e-instruct-fp8": { - id: "llama-4-maverick-17b-128e-instruct-fp8", - name: "Llama-4-Maverick-17B-128E-Instruct-FP8", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - }, - }, - inference: { - id: "inference", - env: ["INFERENCE_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://inference.net/v1", - name: "Inference", - doc: "https://inference.net/models", - models: { - "mistral/mistral-nemo-12b-instruct": { - id: "mistral/mistral-nemo-12b-instruct", - name: "Mistral Nemo 12B Instruct", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.038, output: 0.1 }, - limit: { context: 16000, output: 4096 }, - }, - "google/gemma-3": { - id: "google/gemma-3", - name: "Google Gemma 3", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.3 }, - limit: { context: 125000, output: 4096 }, - }, - "qwen/qwen3-embedding-4b": { - id: "qwen/qwen3-embedding-4b", - name: "Qwen 3 Embedding 4B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2024-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0 }, - limit: { context: 32000, output: 2048 }, - }, - "qwen/qwen-2.5-7b-vision-instruct": { - id: "qwen/qwen-2.5-7b-vision-instruct", - name: "Qwen 2.5 7B Vision Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 125000, output: 4096 }, - }, - "meta/llama-3.2-11b-vision-instruct": { - id: "meta/llama-3.2-11b-vision-instruct", - name: "Llama 3.2 11B Vision Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.055, output: 0.055 }, - limit: { context: 16000, output: 4096 }, - }, - "meta/llama-3.2-3b-instruct": { - id: "meta/llama-3.2-3b-instruct", - name: "Llama 3.2 3B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.02 }, - limit: { context: 16000, output: 4096 }, - }, - "meta/llama-3.2-1b-instruct": { - id: "meta/llama-3.2-1b-instruct", - name: "Llama 3.2 1B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0.01 }, - limit: { context: 16000, output: 4096 }, - }, - "meta/llama-3.1-8b-instruct": { - id: "meta/llama-3.1-8b-instruct", - name: "Llama 3.1 8B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.025, output: 0.025 }, - limit: { context: 16000, output: 4096 }, - }, - "osmosis/osmosis-structure-0.6b": { - id: "osmosis/osmosis-structure-0.6b", - name: "Osmosis Structure 0.6B", - family: "osmosis", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.5 }, - limit: { context: 4000, output: 2048 }, - }, - }, - }, - deepinfra: { - id: "deepinfra", - env: ["DEEPINFRA_API_KEY"], - npm: "@ai-sdk/deepinfra", - name: "Deep Infra", - doc: "https://deepinfra.com/models", - models: { - "zai-org/GLM-4.7-Flash": { - id: "zai-org/GLM-4.7-Flash", - name: "GLM-4.7-Flash", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.4 }, - limit: { context: 202752, output: 16384 }, - }, - "zai-org/GLM-4.6": { - id: "zai-org/GLM-4.6", - name: "GLM-4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.43, output: 1.74, cache_read: 0.08 }, - limit: { context: 204800, output: 131072 }, - }, - "zai-org/GLM-4.7": { - id: "zai-org/GLM-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.43, output: 1.75, cache_read: 0.08 }, - limit: { context: 202752, output: 16384 }, - }, - "zai-org/GLM-4.6V": { - id: "zai-org/GLM-4.6V", - name: "GLM-4.6V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 204800, output: 131072 }, - }, - "zai-org/GLM-4.5": { - id: "zai-org/GLM-4.5", - name: "GLM-4.5", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 131072, output: 98304 }, - status: "deprecated", - }, - "zai-org/GLM-5": { - id: "zai-org/GLM-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-12", - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.8, output: 2.56, cache_read: 0.16 }, - limit: { context: 202752, output: 16384 }, - }, - "MiniMaxAI/MiniMax-M2.5": { - id: "MiniMaxAI/MiniMax-M2.5", - name: "MiniMax M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-06", - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 0.95, cache_read: 0.03, cache_write: 0.375 }, - limit: { context: 204800, output: 131072 }, - }, - "MiniMaxAI/MiniMax-M2": { - id: "MiniMaxAI/MiniMax-M2", - name: "MiniMax M2", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-10", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.254, output: 1.02 }, - limit: { context: 262144, output: 32768 }, - }, - "MiniMaxAI/MiniMax-M2.1": { - id: "MiniMaxAI/MiniMax-M2.1", - name: "MiniMax M2.1", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-06", - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 1.2 }, - limit: { context: 196608, output: 196608 }, - }, - "deepseek-ai/DeepSeek-R1-0528": { - id: "deepseek-ai/DeepSeek-R1-0528", - name: "DeepSeek-R1-0528", - attachment: false, - reasoning: true, - tool_call: false, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-07", - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 2.15, cache_read: 0.35 }, - limit: { context: 163840, output: 64000 }, - }, - "deepseek-ai/DeepSeek-V3.2": { - id: "deepseek-ai/DeepSeek-V3.2", - name: "DeepSeek-V3.2", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-12", - release_date: "2025-12-02", - last_updated: "2025-12-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.26, output: 0.38, cache_read: 0.13 }, - limit: { context: 163840, output: 64000 }, - }, - "moonshotai/Kimi-K2-Instruct": { - id: "moonshotai/Kimi-K2-Instruct", - name: "Kimi K2", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-11", - last_updated: "2025-07-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 2 }, - limit: { context: 131072, output: 32768 }, - }, - "moonshotai/Kimi-K2-Instruct-0905": { - id: "moonshotai/Kimi-K2-Instruct-0905", - name: "Kimi K2 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2, cache_read: 0.15 }, - limit: { context: 262144, output: 262144 }, - }, - "moonshotai/Kimi-K2.5": { - id: "moonshotai/Kimi-K2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 2.8 }, - limit: { context: 262144, output: 32768 }, - }, - "moonshotai/Kimi-K2-Thinking": { - id: "moonshotai/Kimi-K2-Thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-10", - release_date: "2025-11-06", - last_updated: "2025-11-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.47, output: 2 }, - limit: { context: 131072, output: 32768 }, - }, - "meta-llama/Llama-3.1-8B-Instruct-Turbo": { - id: "meta-llama/Llama-3.1-8B-Instruct-Turbo", - name: "Llama 3.1 8B Turbo", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.03 }, - limit: { context: 131072, output: 16384 }, - }, - "meta-llama/Llama-3.1-70B-Instruct-Turbo": { - id: "meta-llama/Llama-3.1-70B-Instruct-Turbo", - name: "Llama 3.1 70B Turbo", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 0.4 }, - limit: { context: 131072, output: 16384 }, - }, - "meta-llama/Llama-4-Scout-17B-16E-Instruct": { - id: "meta-llama/Llama-4-Scout-17B-16E-Instruct", - name: "Llama 4 Scout 17B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.08, output: 0.3 }, - limit: { context: 10000000, output: 16384 }, - }, - "meta-llama/Llama-3.1-70B-Instruct": { - id: "meta-llama/Llama-3.1-70B-Instruct", - name: "Llama 3.1 70B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 0.4 }, - limit: { context: 131072, output: 16384 }, - }, - "meta-llama/Llama-3.1-8B-Instruct": { - id: "meta-llama/Llama-3.1-8B-Instruct", - name: "Llama 3.1 8B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.05 }, - limit: { context: 131072, output: 16384 }, - }, - "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": { - id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", - name: "Llama 4 Maverick 17B FP8", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 1000000, output: 16384 }, - }, - "meta-llama/Llama-3.3-70B-Instruct-Turbo": { - id: "meta-llama/Llama-3.3-70B-Instruct-Turbo", - name: "Llama 3.3 70B Turbo", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.32 }, - limit: { context: 131072, output: 16384 }, - }, - "Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo": { - id: "Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo", - name: "Qwen3 Coder 480B A35B Instruct Turbo", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 262144, output: 66536 }, - }, - "Qwen/Qwen3-Coder-480B-A35B-Instruct": { - id: "Qwen/Qwen3-Coder-480B-A35B-Instruct", - name: "Qwen3 Coder 480B A35B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 1.6 }, - limit: { context: 262144, output: 66536 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.24 }, - limit: { context: 131072, output: 16384 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "GPT OSS 20B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.14 }, - limit: { context: 131072, output: 16384 }, - }, - "anthropic/claude-3-7-sonnet-latest": { - id: "anthropic/claude-3-7-sonnet-latest", - name: "Claude Sonnet 3.7 (Latest)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10-31", - release_date: "2025-03-13", - last_updated: "2025-03-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3.3, output: 16.5, cache_read: 0.33 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-4-opus": { - id: "anthropic/claude-4-opus", - name: "Claude Opus 4", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-06-12", - last_updated: "2025-06-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 16.5, output: 82.5 }, - limit: { context: 200000, output: 32000 }, - }, - }, - }, - "perplexity-agent": { - id: "perplexity-agent", - env: ["PERPLEXITY_API_KEY"], - npm: "@ai-sdk/openai", - api: "https://api.perplexity.ai/v1", - name: "Perplexity Agent", - doc: "https://docs.perplexity.ai/docs/agent-api/models", - models: { - "nvidia/nemotron-3-super-120b-a12b": { - id: "nvidia/nemotron-3-super-120b-a12b", - name: "Nemotron 3 Super 120B", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2026-02", - release_date: "2026-03-11", - last_updated: "2026-03-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 2.5 }, - limit: { context: 1000000, output: 32000 }, - }, - "google/gemini-2.5-flash": { - id: "google/gemini-2.5-flash", - name: "Gemini 2.5 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.03 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-3-flash-preview": { - id: "google/gemini-3-flash-preview", - name: "Gemini 3 Flash Preview", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 0.5, - output: 3, - cache_read: 0.05, - context_over_200k: { input: 0.5, output: 3, cache_read: 0.05 }, - }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-3.1-pro-preview": { - id: "google/gemini-3.1-pro-preview", - name: "Gemini 3.1 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-19", - last_updated: "2026-02-19", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, context_over_200k: { input: 4, output: 18, cache_read: 0.4 } }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-2.5-pro": { - id: "google/gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 1.25, - output: 10, - cache_read: 0.125, - context_over_200k: { input: 2.5, output: 15, cache_read: 0.25 }, - }, - limit: { context: 1048576, output: 65536 }, - }, - "openai/gpt-5.1": { - id: "openai/gpt-5.1", - name: "GPT-5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "openai/gpt-5.2": { - id: "openai/gpt-5.2", - name: "GPT-5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "openai/gpt-5.4": { - id: "openai/gpt-5.4", - name: "GPT-5.4", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 15, cache_read: 0.25 }, - limit: { context: 1050000, input: 922000, output: 128000 }, - }, - "openai/gpt-5-mini": { - id: "openai/gpt-5-mini", - name: "GPT-5 Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.025 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "perplexity/sonar": { - id: "perplexity/sonar", - name: "Sonar", - family: "sonar", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-09-01", - release_date: "2024-01-01", - last_updated: "2025-09-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2.5, cache_read: 0.0625 }, - limit: { context: 128000, output: 8192 }, - }, - "anthropic/claude-opus-4-6": { - id: "anthropic/claude-opus-4-6", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5 }, - limit: { context: 200000, output: 128000 }, - }, - "anthropic/claude-sonnet-4-6": { - id: "anthropic/claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-haiku-4-5": { - id: "anthropic/claude-haiku-4-5", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-opus-4-5": { - id: "anthropic/claude-opus-4-5", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-11-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-sonnet-4-5": { - id: "anthropic/claude-sonnet-4-5", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3 }, - limit: { context: 200000, output: 64000 }, - }, - "xai/grok-4-1-fast-non-reasoning": { - id: "xai/grok-4-1-fast-non-reasoning", - name: "Grok 4.1 Fast (Non-Reasoning)", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - }, - }, - xiaomi: { - id: "xiaomi", - env: ["XIAOMI_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.xiaomimimo.com/v1", - name: "Xiaomi", - doc: "https://platform.xiaomimimo.com/#/docs", - models: { - "mimo-v2-flash": { - id: "mimo-v2-flash", - name: "MiMo-V2-Flash", - family: "mimo", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-12-01", - release_date: "2025-12-16", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3, cache_read: 0.01 }, - limit: { context: 256000, output: 64000 }, - }, - }, - }, - synthetic: { - id: "synthetic", - env: ["SYNTHETIC_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.synthetic.new/openai/v1", - name: "Synthetic", - doc: "https://synthetic.new/pricing", - models: { - "hf:MiniMaxAI/MiniMax-M2.5": { - id: "hf:MiniMaxAI/MiniMax-M2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-07", - last_updated: "2026-02-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3, cache_read: 0.6 }, - limit: { context: 191488, output: 65536 }, - }, - "hf:MiniMaxAI/MiniMax-M2": { - id: "hf:MiniMaxAI/MiniMax-M2", - name: "MiniMax-M2", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-10-27", - last_updated: "2025-10-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.19 }, - limit: { context: 196608, output: 131000 }, - }, - "hf:MiniMaxAI/MiniMax-M2.1": { - id: "hf:MiniMaxAI/MiniMax-M2.1", - name: "MiniMax-M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.19 }, - limit: { context: 204800, output: 131072 }, - }, - "hf:deepseek-ai/DeepSeek-R1": { - id: "hf:deepseek-ai/DeepSeek-R1", - name: "DeepSeek R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.19 }, - limit: { context: 128000, output: 128000 }, - }, - "hf:deepseek-ai/DeepSeek-R1-0528": { - id: "hf:deepseek-ai/DeepSeek-R1-0528", - name: "DeepSeek R1 (0528)", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-01", - last_updated: "2025-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 8 }, - limit: { context: 128000, output: 128000 }, - }, - "hf:deepseek-ai/DeepSeek-V3.1": { - id: "hf:deepseek-ai/DeepSeek-V3.1", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.56, output: 1.68 }, - limit: { context: 128000, output: 128000 }, - }, - "hf:deepseek-ai/DeepSeek-V3.2": { - id: "hf:deepseek-ai/DeepSeek-V3.2", - name: "DeepSeek V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 0.4, cache_read: 0.27, cache_write: 0 }, - limit: { context: 162816, input: 162816, output: 8000 }, - }, - "hf:deepseek-ai/DeepSeek-V3-0324": { - id: "hf:deepseek-ai/DeepSeek-V3-0324", - name: "DeepSeek V3 (0324)", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-08-01", - last_updated: "2025-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.2, output: 1.2 }, - limit: { context: 128000, output: 128000 }, - }, - "hf:deepseek-ai/DeepSeek-V3": { - id: "hf:deepseek-ai/DeepSeek-V3", - name: "DeepSeek V3", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-01-20", - last_updated: "2025-05-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.25, output: 1.25 }, - limit: { context: 128000, output: 128000 }, - }, - "hf:deepseek-ai/DeepSeek-V3.1-Terminus": { - id: "hf:deepseek-ai/DeepSeek-V3.1-Terminus", - name: "DeepSeek V3.1 Terminus", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-09-22", - last_updated: "2025-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.2, output: 1.2 }, - limit: { context: 128000, output: 128000 }, - }, - "hf:moonshotai/Kimi-K2-Instruct-0905": { - id: "hf:moonshotai/Kimi-K2-Instruct-0905", - name: "Kimi K2 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.2, output: 1.2 }, - limit: { context: 262144, output: 32768 }, - }, - "hf:moonshotai/Kimi-K2.5": { - id: "hf:moonshotai/Kimi-K2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.19 }, - limit: { context: 262144, output: 65536 }, - }, - "hf:moonshotai/Kimi-K2-Thinking": { - id: "hf:moonshotai/Kimi-K2-Thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-07", - last_updated: "2025-11-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.19 }, - limit: { context: 262144, output: 262144 }, - }, - "hf:openai/gpt-oss-120b": { - id: "hf:openai/gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 128000, output: 32768 }, - }, - "hf:nvidia/Kimi-K2.5-NVFP4": { - id: "hf:nvidia/Kimi-K2.5-NVFP4", - name: "Kimi K2.5 (NVFP4)", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.19 }, - limit: { context: 262144, output: 65536 }, - }, - "hf:meta-llama/Llama-4-Scout-17B-16E-Instruct": { - id: "hf:meta-llama/Llama-4-Scout-17B-16E-Instruct", - name: "Llama-4-Scout-17B-16E-Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 328000, output: 4096 }, - }, - "hf:meta-llama/Llama-3.1-405B-Instruct": { - id: "hf:meta-llama/Llama-3.1-405B-Instruct", - name: "Llama-3.1-405B-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 3, output: 3 }, - limit: { context: 128000, output: 32768 }, - }, - "hf:meta-llama/Llama-3.1-70B-Instruct": { - id: "hf:meta-llama/Llama-3.1-70B-Instruct", - name: "Llama-3.1-70B-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.9, output: 0.9 }, - limit: { context: 128000, output: 32768 }, - }, - "hf:meta-llama/Llama-3.1-8B-Instruct": { - id: "hf:meta-llama/Llama-3.1-8B-Instruct", - name: "Llama-3.1-8B-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 128000, output: 32768 }, - }, - "hf:meta-llama/Llama-3.3-70B-Instruct": { - id: "hf:meta-llama/Llama-3.3-70B-Instruct", - name: "Llama-3.3-70B-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.9, output: 0.9 }, - limit: { context: 128000, output: 32768 }, - }, - "hf:meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": { - id: "hf:meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", - name: "Llama-4-Maverick-17B-128E-Instruct-FP8", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.22, output: 0.88 }, - limit: { context: 524000, output: 4096 }, - }, - "hf:zai-org/GLM-4.7-Flash": { - id: "hf:zai-org/GLM-4.7-Flash", - name: "GLM-4.7-Flash", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-01-18", - last_updated: "2026-01-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.4, cache_read: 0.06 }, - limit: { context: 196608, output: 65536 }, - }, - "hf:zai-org/GLM-4.6": { - id: "hf:zai-org/GLM-4.6", - name: "GLM 4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.19 }, - limit: { context: 200000, output: 64000 }, - }, - "hf:zai-org/GLM-4.7": { - id: "hf:zai-org/GLM-4.7", - name: "GLM 4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.19 }, - limit: { context: 200000, output: 64000 }, - }, - "hf:Qwen/Qwen3-235B-A22B-Thinking-2507": { - id: "hf:Qwen/Qwen3-235B-A22B-Thinking-2507", - name: "Qwen3 235B A22B Thinking 2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-25", - last_updated: "2025-07-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.65, output: 3 }, - limit: { context: 256000, output: 32000 }, - }, - "hf:Qwen/Qwen2.5-Coder-32B-Instruct": { - id: "hf:Qwen/Qwen2.5-Coder-32B-Instruct", - name: "Qwen2.5-Coder-32B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2024-11-11", - last_updated: "2024-11-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.8, output: 0.8 }, - limit: { context: 32768, output: 32768 }, - }, - "hf:Qwen/Qwen3-Coder-480B-A35B-Instruct": { - id: "hf:Qwen/Qwen3-Coder-480B-A35B-Instruct", - name: "Qwen 3 Coder 480B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 2 }, - limit: { context: 256000, output: 32000 }, - }, - "hf:Qwen/Qwen3-235B-A22B-Instruct-2507": { - id: "hf:Qwen/Qwen3-235B-A22B-Instruct-2507", - name: "Qwen 3 235B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-28", - last_updated: "2025-07-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 256000, output: 32000 }, - }, - }, - }, - nebius: { - id: "nebius", - env: ["NEBIUS_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.tokenfactory.nebius.com/v1", - name: "Nebius Token Factory", - doc: "https://docs.tokenfactory.nebius.com/", - models: { - "zai-org/GLM-4.7-FP8": { - id: "zai-org/GLM-4.7-FP8", - name: "GLM-4.7 (FP8)", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-12", - release_date: "2026-01-15", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2, cache_read: 0.04, cache_write: 0.5 }, - limit: { context: 128000, input: 124000, output: 4096 }, - }, - "zai-org/GLM-4.5-Air": { - id: "zai-org/GLM-4.5-Air", - name: "GLM-4.5-Air", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-11-15", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.2, cache_read: 0.02, cache_write: 0.25 }, - limit: { context: 128000, input: 124000, output: 4096 }, - }, - "zai-org/GLM-4.5": { - id: "zai-org/GLM-4.5", - name: "GLM-4.5", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-11-15", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 2.2, cache_read: 0.06, cache_write: 0.75 }, - limit: { context: 128000, input: 124000, output: 4096 }, - }, - "zai-org/GLM-5": { - id: "zai-org/GLM-5", - name: "GLM-5", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2026-01", - release_date: "2026-03-01", - last_updated: "2026-03-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 3.2, cache_read: 0.1, cache_write: 1 }, - limit: { context: 200000, input: 200000, output: 16384 }, - }, - "nvidia/nemotron-3-super-120b-a12b": { - id: "nvidia/nemotron-3-super-120b-a12b", - name: "Nemotron-3-Super-120B-A12B", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2026-02", - release_date: "2026-03-11", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 256000, input: 256000, output: 32768 }, - }, - "nvidia/Llama-3_1-Nemotron-Ultra-253B-v1": { - id: "nvidia/Llama-3_1-Nemotron-Ultra-253B-v1", - name: "Llama-3.1-Nemotron-Ultra-253B-v1", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-15", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 1.8, cache_read: 0.06, cache_write: 0.75 }, - limit: { context: 128000, input: 120000, output: 4096 }, - }, - "nvidia/Nemotron-Nano-V2-12b": { - id: "nvidia/Nemotron-Nano-V2-12b", - name: "Nemotron-Nano-V2-12b", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-15", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.2, cache_read: 0.007, cache_write: 0.08 }, - limit: { context: 32000, input: 30000, output: 4096 }, - }, - "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B": { - id: "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B", - name: "Nemotron-3-Nano-30B-A3B", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-08-10", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.24, cache_read: 0.006, cache_write: 0.075 }, - limit: { context: 32000, input: 30000, output: 4096 }, - }, - "NousResearch/Hermes-4-405B": { - id: "NousResearch/Hermes-4-405B", - name: "Hermes-4-405B", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-11", - release_date: "2026-01-30", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3, reasoning: 3, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "NousResearch/Hermes-4-70B": { - id: "NousResearch/Hermes-4-70B", - name: "Hermes-4-70B", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-11", - release_date: "2026-01-30", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.4, reasoning: 0.4, cache_read: 0.013, cache_write: 0.16 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "BAAI/bge-en-icl": { - id: "BAAI/bge-en-icl", - name: "BGE-ICL", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: false, - knowledge: "2024-06", - release_date: "2024-07-30", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0 }, - limit: { context: 32768, input: 32768, output: 0 }, - }, - "BAAI/bge-multilingual-gemma2": { - id: "BAAI/bge-multilingual-gemma2", - name: "bge-multilingual-gemma2", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: false, - knowledge: "2024-06", - release_date: "2024-07-30", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0 }, - limit: { context: 8192, input: 8192, output: 0 }, - }, - "PrimeIntellect/INTELLECT-3": { - id: "PrimeIntellect/INTELLECT-3", - name: "INTELLECT-3", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-10", - release_date: "2026-01-25", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 1.1, cache_read: 0.02, cache_write: 0.25 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "MiniMaxAI/MiniMax-M2.1": { - id: "MiniMaxAI/MiniMax-M2.1", - name: "MiniMax-M2.1", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-10", - release_date: "2026-02-01", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, reasoning: 1.2, cache_read: 0.03, cache_write: 0.375 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "deepseek-ai/DeepSeek-V3-0324-fast": { - id: "deepseek-ai/DeepSeek-V3-0324-fast", - name: "DeepSeek-V3-0324 (Fast)", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-03-24", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.75, output: 2.25, cache_read: 0.075, cache_write: 0.28125 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "deepseek-ai/DeepSeek-R1-0528": { - id: "deepseek-ai/DeepSeek-R1-0528", - name: "DeepSeek-R1-0528", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-11", - release_date: "2026-01-15", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.8, output: 2.4, reasoning: 2.4, cache_read: 0.08, cache_write: 1 }, - limit: { context: 128000, input: 120000, output: 32768 }, - }, - "deepseek-ai/DeepSeek-V3.2": { - id: "deepseek-ai/DeepSeek-V3.2", - name: "DeepSeek-V3.2", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-11", - release_date: "2026-01-20", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.45, reasoning: 0.45, cache_read: 0.03, cache_write: 0.375 }, - limit: { context: 163000, input: 160000, output: 16384 }, - }, - "deepseek-ai/DeepSeek-V3-0324": { - id: "deepseek-ai/DeepSeek-V3-0324", - name: "DeepSeek-V3-0324", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-03-24", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 1.5, cache_read: 0.05, cache_write: 0.1875 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "deepseek-ai/DeepSeek-R1-0528-fast": { - id: "deepseek-ai/DeepSeek-R1-0528-fast", - name: "DeepSeek R1 0528 Fast", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 6 }, - limit: { context: 131072, output: 8192 }, - }, - "intfloat/e5-mistral-7b-instruct": { - id: "intfloat/e5-mistral-7b-instruct", - name: "e5-mistral-7b-instruct", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: false, - knowledge: "2023-12", - release_date: "2024-01-01", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0 }, - limit: { context: 32768, input: 32768, output: 0 }, - }, - "moonshotai/Kimi-K2-Instruct": { - id: "moonshotai/Kimi-K2-Instruct", - name: "Kimi-K2-Instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-10", - release_date: "2026-01-05", - last_updated: "2026-02-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 2.4, cache_read: 0.05, cache_write: 0.625 }, - limit: { context: 200000, input: 190000, output: 8192 }, - }, - "moonshotai/Kimi-K2.5-fast": { - id: "moonshotai/Kimi-K2.5-fast", - name: "Kimi-K2.5-fast", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-12-15", - last_updated: "2026-02-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 2.5, cache_read: 0.05, cache_write: 0.625 }, - limit: { context: 256000, input: 256000, output: 8192 }, - }, - "moonshotai/Kimi-K2.5": { - id: "moonshotai/Kimi-K2.5", - name: "Kimi-K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-12-15", - last_updated: "2026-02-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 2.5, reasoning: 2.5, cache_read: 0.05, cache_write: 0.625 }, - limit: { context: 256000, input: 256000, output: 8192 }, - }, - "moonshotai/Kimi-K2-Thinking": { - id: "moonshotai/Kimi-K2-Thinking", - name: "Kimi-K2-Thinking", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-10", - release_date: "2026-01-05", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5, reasoning: 2.5, cache_read: 0.06, cache_write: 0.75 }, - limit: { context: 128000, input: 120000, output: 16384 }, - }, - "google/gemma-2-2b-it": { - id: "google/gemma-2-2b-it", - name: "Gemma-2-2b-it", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - knowledge: "2024-06", - release_date: "2024-07-31", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.06, cache_read: 0.002, cache_write: 0.025 }, - limit: { context: 8192, input: 8000, output: 4096 }, - }, - "google/gemma-3-27b-it-fast": { - id: "google/gemma-3-27b-it-fast", - name: "Gemma-3-27b-it (Fast)", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-10", - release_date: "2026-01-20", - last_updated: "2026-02-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.6, cache_read: 0.02, cache_write: 0.25 }, - limit: { context: 110000, input: 100000, output: 8192 }, - }, - "google/gemma-2-9b-it-fast": { - id: "google/gemma-2-9b-it-fast", - name: "Gemma-2-9b-it (Fast)", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - knowledge: "2024-06", - release_date: "2024-06-27", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.09, cache_read: 0.003, cache_write: 0.0375 }, - limit: { context: 8192, input: 8000, output: 4096 }, - }, - "google/gemma-3-27b-it": { - id: "google/gemma-3-27b-it", - name: "Gemma-3-27b-it", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-10", - release_date: "2026-01-20", - last_updated: "2026-02-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3, cache_read: 0.01, cache_write: 0.125 }, - limit: { context: 110000, input: 100000, output: 8192 }, - }, - "meta-llama/Meta-Llama-3.1-8B-Instruct": { - id: "meta-llama/Meta-Llama-3.1-8B-Instruct", - name: "Meta-Llama-3.1-8B-Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-12", - release_date: "2024-07-23", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.06, cache_read: 0.002, cache_write: 0.025 }, - limit: { context: 128000, input: 120000, output: 4096 }, - }, - "meta-llama/Llama-Guard-3-8B": { - id: "meta-llama/Llama-Guard-3-8B", - name: "Llama-Guard-3-8B", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: false, - knowledge: "2024-04", - release_date: "2024-04-18", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.06, cache_read: 0.002, cache_write: 0.025 }, - limit: { context: 8192, input: 8000, output: 1024 }, - }, - "meta-llama/Llama-3.3-70B-Instruct-fast": { - id: "meta-llama/Llama-3.3-70B-Instruct-fast", - name: "Llama-3.3-70B-Instruct (Fast)", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-12-05", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 0.75, cache_read: 0.025, cache_write: 0.31 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "meta-llama/Meta-Llama-3.1-8B-Instruct-fast": { - id: "meta-llama/Meta-Llama-3.1-8B-Instruct-fast", - name: "Meta-Llama-3.1-8B-Instruct (Fast)", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-12", - release_date: "2024-07-23", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.09, cache_read: 0.003, cache_write: 0.03 }, - limit: { context: 128000, input: 120000, output: 4096 }, - }, - "meta-llama/Llama-3.3-70B-Instruct": { - id: "meta-llama/Llama-3.3-70B-Instruct", - name: "Llama-3.3-70B-Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-12-05", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.4, cache_read: 0.013, cache_write: 0.16 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "Qwen/Qwen3-30B-A3B-Instruct-2507": { - id: "Qwen/Qwen3-30B-A3B-Instruct-2507", - name: "Qwen3-30B-A3B-Instruct-2507", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-12", - release_date: "2026-01-28", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3, cache_read: 0.01, cache_write: 0.125 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "Qwen/Qwen3-32B": { - id: "Qwen/Qwen3-32B", - name: "Qwen3-32B", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-12", - release_date: "2026-01-28", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3, cache_read: 0.01, cache_write: 0.125 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "Qwen/Qwen3-235B-A22B-Thinking-2507": { - id: "Qwen/Qwen3-235B-A22B-Thinking-2507", - name: "Qwen3 235B A22B Thinking 2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-25", - last_updated: "2025-10-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 262144, output: 8192 }, - }, - "Qwen/Qwen3-30B-A3B-Thinking-2507": { - id: "Qwen/Qwen3-30B-A3B-Thinking-2507", - name: "Qwen3-30B-A3B-Thinking-2507", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-12", - release_date: "2026-01-28", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3, reasoning: 0.3, cache_read: 0.01, cache_write: 0.125 }, - limit: { context: 128000, input: 120000, output: 16384 }, - }, - "Qwen/Qwen3-Coder-480B-A35B-Instruct": { - id: "Qwen/Qwen3-Coder-480B-A35B-Instruct", - name: "Qwen3 Coder 480B A35B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-10-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.8 }, - limit: { context: 262144, output: 66536 }, - }, - "Qwen/Qwen2.5-VL-72B-Instruct": { - id: "Qwen/Qwen2.5-VL-72B-Instruct", - name: "Qwen2.5-VL-72B-Instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-20", - last_updated: "2026-02-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 0.75, cache_read: 0.025, cache_write: 0.31 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "Qwen/Qwen3-Embedding-8B": { - id: "Qwen/Qwen3-Embedding-8B", - name: "Qwen3-Embedding-8B", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: false, - knowledge: "2025-10", - release_date: "2026-01-10", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0 }, - limit: { context: 32768, input: 32768, output: 0 }, - }, - "Qwen/Qwen3-32B-fast": { - id: "Qwen/Qwen3-32B-fast", - name: "Qwen3-32B (Fast)", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-12", - release_date: "2026-01-28", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.6, cache_read: 0.02, cache_write: 0.25 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "Qwen/Qwen3-Next-80B-A3B-Thinking": { - id: "Qwen/Qwen3-Next-80B-A3B-Thinking", - name: "Qwen3-Next-80B-A3B-Thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-12", - release_date: "2026-01-28", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 1.2, reasoning: 1.2, cache_read: 0.015, cache_write: 0.18 }, - limit: { context: 128000, input: 120000, output: 16384 }, - }, - "Qwen/Qwen2.5-Coder-7B-fast": { - id: "Qwen/Qwen2.5-Coder-7B-fast", - name: "Qwen2.5-Coder-7B (Fast)", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-09", - release_date: "2024-09-19", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.09, cache_read: 0.003, cache_write: 0.03 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "Qwen/Qwen3-Coder-30B-A3B-Instruct": { - id: "Qwen/Qwen3-Coder-30B-A3B-Instruct", - name: "Qwen3-Coder-30B-A3B-Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-12", - release_date: "2026-01-28", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3, cache_read: 0.01, cache_write: 0.125 }, - limit: { context: 128000, input: 120000, output: 8192 }, - }, - "Qwen/Qwen3-235B-A22B-Instruct-2507": { - id: "Qwen/Qwen3-235B-A22B-Instruct-2507", - name: "Qwen3 235B A22B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-25", - last_updated: "2025-10-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 262144, output: 8192 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "gpt-oss-120b", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-09", - release_date: "2026-01-10", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6, reasoning: 0.6, cache_read: 0.015, cache_write: 0.18 }, - limit: { context: 128000, input: 124000, output: 8192 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "gpt-oss-20b", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-09", - release_date: "2026-01-10", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.2, cache_read: 0.005, cache_write: 0.06 }, - limit: { context: 128000, input: 124000, output: 4096 }, - }, - "black-forest-labs/flux-dev": { - id: "black-forest-labs/flux-dev", - name: "FLUX.1-dev", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: false, - knowledge: "2024-07", - release_date: "2024-08-01", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["image"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 77, input: 77, output: 0 }, - }, - "black-forest-labs/flux-schnell": { - id: "black-forest-labs/flux-schnell", - name: "FLUX.1-schnell", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: false, - knowledge: "2024-07", - release_date: "2024-08-01", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["image"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 77, input: 77, output: 0 }, - }, - }, - }, - "qiniu-ai": { - id: "qiniu-ai", - env: ["QINIU_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.qnaigc.com/v1", - name: "Qiniu", - doc: "https://developer.qiniu.com/aitokenapi", - models: { - "claude-4.5-haiku": { - id: "claude-4.5-haiku", - name: "Claude 4.5 Haiku", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-10-16", - last_updated: "2025-10-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 64000 }, - }, - "claude-3.5-sonnet": { - id: "claude-3.5-sonnet", - name: "Claude 3.5 Sonnet", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-09-09", - last_updated: "2025-09-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 8200 }, - }, - "qwen3-235b-a22b-instruct-2507": { - id: "qwen3-235b-a22b-instruct-2507", - name: "Qwen3 235b A22B Instruct 2507", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-12", - last_updated: "2025-08-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 262144, output: 64000 }, - }, - "kimi-k2": { - id: "kimi-k2", - name: "Kimi K2", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 128000 }, - }, - "claude-3.7-sonnet": { - id: "claude-3.7-sonnet", - name: "Claude 3.7 Sonnet", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 128000 }, - }, - "qwen3-max-preview": { - id: "qwen3-max-preview", - name: "Qwen3 Max Preview", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-09-06", - last_updated: "2025-09-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 64000 }, - }, - "qwen3-next-80b-a3b-thinking": { - id: "qwen3-next-80b-a3b-thinking", - name: "Qwen3 Next 80B A3B Thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-09-12", - last_updated: "2025-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 131072, output: 32768 }, - }, - "claude-4.0-sonnet": { - id: "claude-4.0-sonnet", - name: "Claude 4.0 Sonnet", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 64000 }, - }, - "qwen-vl-max-2025-01-25": { - id: "qwen-vl-max-2025-01-25", - name: "Qwen VL-MAX-2025-01-25", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 4096 }, - }, - "deepseek-v3": { - id: "deepseek-v3", - name: "DeepSeek-V3", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-08-13", - last_updated: "2025-08-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 16000 }, - }, - "doubao-seed-1.6-thinking": { - id: "doubao-seed-1.6-thinking", - name: "Doubao-Seed 1.6 Thinking", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-15", - last_updated: "2025-08-15", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 32000 }, - }, - "qwen3-coder-480b-a35b-instruct": { - id: "qwen3-coder-480b-a35b-instruct", - name: "Qwen3 Coder 480B A35B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-14", - last_updated: "2025-08-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 262000, output: 4096 }, - }, - "mimo-v2-flash": { - id: "mimo-v2-flash", - name: "Mimo-V2-Flash", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 256000 }, - }, - "glm-4.5-air": { - id: "glm-4.5-air", - name: "GLM 4.5 Air", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 131000, output: 4096 }, - }, - "glm-4.5": { - id: "glm-4.5", - name: "GLM 4.5", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 131072, output: 98304 }, - }, - "claude-4.5-sonnet": { - id: "claude-4.5-sonnet", - name: "Claude 4.5 Sonnet", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 64000 }, - }, - "qwen2.5-vl-7b-instruct": { - id: "qwen2.5-vl-7b-instruct", - name: "Qwen 2.5 VL 7B Instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 8192 }, - }, - "doubao-seed-2.0-pro": { - id: "doubao-seed-2.0-pro", - name: "Doubao Seed 2.0 Pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 128000 }, - }, - "gemini-2.5-flash": { - id: "gemini-2.5-flash", - name: "Gemini 2.5 Flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 1048576, output: 64000 }, - }, - "deepseek-v3.1": { - id: "deepseek-v3.1", - name: "DeepSeek-V3.1", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-19", - last_updated: "2025-08-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 32000 }, - }, - "doubao-seed-1.6": { - id: "doubao-seed-1.6", - name: "Doubao-Seed 1.6", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-15", - last_updated: "2025-08-15", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 32000 }, - }, - "doubao-seed-2.0-mini": { - id: "doubao-seed-2.0-mini", - name: "Doubao Seed 2.0 Mini", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 32000 }, - }, - "claude-4.0-opus": { - id: "claude-4.0-opus", - name: "Claude 4.0 Opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 32000 }, - }, - "qwen-turbo": { - id: "qwen-turbo", - name: "Qwen-Turbo", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 1000000, output: 4096 }, - }, - "gemini-3.0-pro-preview": { - id: "gemini-3.0-pro-preview", - name: "Gemini 3.0 Pro Preview", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text", "image", "video", "pdf", "audio"], output: ["text"] }, - open_weights: false, - limit: { context: 1000000, output: 64000 }, - }, - "deepseek-r1-0528": { - id: "deepseek-r1-0528", - name: "DeepSeek-R1-0528", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 32000 }, - }, - "deepseek-r1": { - id: "deepseek-r1", - name: "DeepSeek-R1", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 32000 }, - }, - "qwen3-32b": { - id: "qwen3-32b", - name: "Qwen3 32B", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 40000, output: 4096 }, - }, - "doubao-1.5-vision-pro": { - id: "doubao-1.5-vision-pro", - name: "Doubao 1.5 Vision Pro", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 16000 }, - }, - "gemini-3.0-pro-image-preview": { - id: "gemini-3.0-pro-image-preview", - name: "Gemini 3.0 Pro Image Preview", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-11-20", - last_updated: "2025-11-20", - modalities: { input: ["text", "image"], output: ["text", "image"] }, - open_weights: false, - limit: { context: 32768, output: 8192 }, - }, - "qwen3.5-397b-a17b": { - id: "qwen3.5-397b-a17b", - name: "Qwen3.5 397B A17B", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-02-22", - last_updated: "2026-02-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 64000 }, - }, - "gemini-2.5-flash-lite": { - id: "gemini-2.5-flash-lite", - name: "Gemini 2.5 Flash Lite", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 1048576, output: 64000 }, - }, - "claude-3.5-haiku": { - id: "claude-3.5-haiku", - name: "Claude 3.5 Haiku", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-26", - last_updated: "2025-08-26", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 8192 }, - }, - "gpt-oss-120b": { - id: "gpt-oss-120b", - name: "gpt-oss-120b", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-06", - last_updated: "2025-08-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 4096 }, - }, - "deepseek-v3-0324": { - id: "deepseek-v3-0324", - name: "DeepSeek-V3-0324", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 16000 }, - }, - "doubao-1.5-pro-32k": { - id: "doubao-1.5-pro-32k", - name: "Doubao 1.5 Pro 32k", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 12000 }, - }, - "qwen3-30b-a3b-instruct-2507": { - id: "qwen3-30b-a3b-instruct-2507", - name: "Qwen3 30b A3b Instruct 2507", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-02-04", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 32000 }, - }, - "qwen2.5-vl-72b-instruct": { - id: "qwen2.5-vl-72b-instruct", - name: "Qwen 2.5 VL 72B Instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 8192 }, - }, - "qwen3-235b-a22b": { - id: "qwen3-235b-a22b", - name: "Qwen 3 235B A22B", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 32000 }, - }, - "doubao-seed-2.0-lite": { - id: "doubao-seed-2.0-lite", - name: "Doubao Seed 2.0 Lite", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 32000 }, - }, - "claude-4.1-opus": { - id: "claude-4.1-opus", - name: "Claude 4.1 Opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-06", - last_updated: "2025-08-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 32000 }, - }, - "doubao-1.5-thinking-pro": { - id: "doubao-1.5-thinking-pro", - name: "Doubao 1.5 Thinking Pro", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 16000 }, - }, - "gemini-2.5-flash-image": { - id: "gemini-2.5-flash-image", - name: "Gemini 2.5 Flash Image", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-10-22", - last_updated: "2025-10-22", - modalities: { input: ["text", "image"], output: ["image"] }, - open_weights: false, - limit: { context: 32768, output: 8192 }, - }, - "MiniMax-M1": { - id: "MiniMax-M1", - name: "MiniMax M1", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 1000000, output: 80000 }, - }, - "doubao-seed-1.6-flash": { - id: "doubao-seed-1.6-flash", - name: "Doubao-Seed 1.6 Flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-15", - last_updated: "2025-08-15", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 32000 }, - }, - "qwen3-vl-30b-a3b-thinking": { - id: "qwen3-vl-30b-a3b-thinking", - name: "Qwen3-Vl 30b A3b Thinking", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-02-09", - last_updated: "2026-02-09", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 32000 }, - }, - "doubao-seed-2.0-code": { - id: "doubao-seed-2.0-code", - name: "Doubao Seed 2.0 Code", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 128000 }, - }, - "qwen3-30b-a3b-thinking-2507": { - id: "qwen3-30b-a3b-thinking-2507", - name: "Qwen3 30b A3b Thinking 2507", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-02-04", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 126000, output: 32000 }, - }, - "claude-4.5-opus": { - id: "claude-4.5-opus", - name: "Claude 4.5 Opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-11-25", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 200000 }, - }, - "qwen3-235b-a22b-thinking-2507": { - id: "qwen3-235b-a22b-thinking-2507", - name: "Qwen3 235B A22B Thinking 2507", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-12", - last_updated: "2025-08-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 262144, output: 4096 }, - }, - "gemini-2.0-flash-lite": { - id: "gemini-2.0-flash-lite", - name: "Gemini 2.0 Flash Lite", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 1048576, output: 8192 }, - }, - "qwen3-next-80b-a3b-instruct": { - id: "qwen3-next-80b-a3b-instruct", - name: "Qwen3 Next 80B A3B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-09-12", - last_updated: "2025-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 131072, output: 32768 }, - }, - "gemini-3.0-flash-preview": { - id: "gemini-3.0-flash-preview", - name: "Gemini 3.0 Flash Preview", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-18", - last_updated: "2025-12-18", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - limit: { context: 1000000, output: 64000 }, - }, - "qwen3-max": { - id: "qwen3-max", - name: "Qwen3 Max", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-09-24", - last_updated: "2025-09-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 262144, output: 65536 }, - }, - "qwen3-30b-a3b": { - id: "qwen3-30b-a3b", - name: "Qwen3 30B A3B", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 40000, output: 4096 }, - }, - "gpt-oss-20b": { - id: "gpt-oss-20b", - name: "gpt-oss-20b", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-06", - last_updated: "2025-08-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 4096 }, - }, - "kling-v2-6": { - id: "kling-v2-6", - name: "Kling-V2 6", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2026-01-13", - last_updated: "2026-01-13", - modalities: { input: ["text", "image", "video"], output: ["video"] }, - open_weights: false, - limit: { context: 99999999, output: 99999999 }, - }, - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - name: "Gemini 2.5 Pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.0-flash": { - id: "gemini-2.0-flash", - name: "Gemini 2.0 Flash", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 1048576, output: 8192 }, - }, - "qwen-max-2025-01-25": { - id: "qwen-max-2025-01-25", - name: "Qwen2.5-Max-2025-01-25", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 4096 }, - }, - "xiaomi/mimo-v2-flash": { - id: "xiaomi/mimo-v2-flash", - name: "Xiaomi/Mimo-V2-Flash", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-12-26", - last_updated: "2025-12-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 256000 }, - }, - "stepfun/step-3.5-flash": { - id: "stepfun/step-3.5-flash", - name: "Stepfun/Step-3.5 Flash", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2026-02-02", - last_updated: "2026-02-02", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 64000, output: 4096 }, - }, - "deepseek/deepseek-v3.2-exp-thinking": { - id: "deepseek/deepseek-v3.2-exp-thinking", - name: "DeepSeek/DeepSeek-V3.2-Exp-Thinking", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 32000 }, - }, - "deepseek/deepseek-v3.1-terminus": { - id: "deepseek/deepseek-v3.1-terminus", - name: "DeepSeek/DeepSeek-V3.1-Terminus", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-09-22", - last_updated: "2025-09-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 32000 }, - }, - "deepseek/deepseek-v3.2-251201": { - id: "deepseek/deepseek-v3.2-251201", - name: "Deepseek/DeepSeek-V3.2", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 32000 }, - }, - "deepseek/deepseek-math-v2": { - id: "deepseek/deepseek-math-v2", - name: "Deepseek/Deepseek-Math-V2", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-12-04", - last_updated: "2025-12-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 160000, output: 160000 }, - }, - "deepseek/deepseek-v3.2-exp": { - id: "deepseek/deepseek-v3.2-exp", - name: "DeepSeek/DeepSeek-V3.2-Exp", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 32000 }, - }, - "deepseek/deepseek-v3.1-terminus-thinking": { - id: "deepseek/deepseek-v3.1-terminus-thinking", - name: "DeepSeek/DeepSeek-V3.1-Terminus-Thinking", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-09-22", - last_updated: "2025-09-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 32000 }, - }, - "moonshotai/kimi-k2-0905": { - id: "moonshotai/kimi-k2-0905", - name: "Kimi K2 0905", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-09-08", - last_updated: "2025-09-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 100000 }, - }, - "moonshotai/kimi-k2.5": { - id: "moonshotai/kimi-k2.5", - name: "Moonshotai/Kimi-K2.5", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-01-28", - last_updated: "2026-01-28", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 256000 }, - }, - "moonshotai/kimi-k2-thinking": { - id: "moonshotai/kimi-k2-thinking", - name: "Kimi K2 Thinking", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-11-07", - last_updated: "2025-11-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 100000 }, - }, - "z-ai/autoglm-phone-9b": { - id: "z-ai/autoglm-phone-9b", - name: "Z-Ai/Autoglm Phone 9b", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 12800, output: 4096 }, - }, - "z-ai/glm-5": { - id: "z-ai/glm-5", - name: "Z-Ai/GLM 5", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 128000 }, - }, - "z-ai/glm-4.6": { - id: "z-ai/glm-4.6", - name: "Z-AI/GLM 4.6", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-10-11", - last_updated: "2025-10-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 200000 }, - }, - "z-ai/glm-4.7": { - id: "z-ai/glm-4.7", - name: "Z-Ai/GLM 4.7", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 200000 }, - }, - "stepfun-ai/gelab-zero-4b-preview": { - id: "stepfun-ai/gelab-zero-4b-preview", - name: "Stepfun-Ai/Gelab Zero 4b Preview", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 8192, output: 4096 }, - }, - "meituan/longcat-flash-lite": { - id: "meituan/longcat-flash-lite", - name: "Meituan/Longcat-Flash-Lite", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-02-06", - last_updated: "2026-02-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 320000 }, - }, - "meituan/longcat-flash-chat": { - id: "meituan/longcat-flash-chat", - name: "Meituan/Longcat-Flash-Chat", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-11-05", - last_updated: "2025-11-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 131072, output: 131072 }, - }, - "x-ai/grok-4-fast-reasoning": { - id: "x-ai/grok-4-fast-reasoning", - name: "X-Ai/Grok-4-Fast-Reasoning", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-18", - last_updated: "2025-12-18", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 2000000, output: 2000000 }, - }, - "x-ai/grok-code-fast-1": { - id: "x-ai/grok-code-fast-1", - name: "x-AI/Grok-Code-Fast 1", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-09-02", - last_updated: "2025-09-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 10000 }, - }, - "x-ai/grok-4.1-fast-reasoning": { - id: "x-ai/grok-4.1-fast-reasoning", - name: "X-Ai/Grok 4.1 Fast Reasoning", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-19", - last_updated: "2025-12-19", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 20000000, output: 2000000 }, - }, - "x-ai/grok-4-fast": { - id: "x-ai/grok-4-fast", - name: "x-AI/Grok-4-Fast", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-09-20", - last_updated: "2025-09-20", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 2000000, output: 2000000 }, - }, - "x-ai/grok-4.1-fast-non-reasoning": { - id: "x-ai/grok-4.1-fast-non-reasoning", - name: "X-Ai/Grok 4.1 Fast Non Reasoning", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-19", - last_updated: "2025-12-19", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 2000000, output: 2000000 }, - }, - "x-ai/grok-4.1-fast": { - id: "x-ai/grok-4.1-fast", - name: "x-AI/Grok-4.1-Fast", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-11-20", - last_updated: "2025-11-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 2000000, output: 2000000 }, - }, - "x-ai/grok-4-fast-non-reasoning": { - id: "x-ai/grok-4-fast-non-reasoning", - name: "X-Ai/Grok-4-Fast-Non-Reasoning", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-18", - last_updated: "2025-12-18", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 2000000, output: 2000000 }, - }, - "openai/gpt-5.2": { - id: "openai/gpt-5.2", - name: "OpenAI/GPT-5.2", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5": { - id: "openai/gpt-5", - name: "OpenAI/GPT-5", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-09-19", - last_updated: "2025-09-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 400000, output: 128000 }, - }, - "minimax/minimax-m2.5-highspeed": { - id: "minimax/minimax-m2.5-highspeed", - name: "Minimax/Minimax-M2.5 Highspeed", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 204800, output: 128000 }, - }, - "minimax/minimax-m2.1": { - id: "minimax/minimax-m2.1", - name: "Minimax/Minimax-M2.1", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 204800, output: 128000 }, - }, - "minimax/minimax-m2": { - id: "minimax/minimax-m2", - name: "Minimax/Minimax-M2", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-10-28", - last_updated: "2025-10-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 128000 }, - }, - "minimax/minimax-m2.5": { - id: "minimax/minimax-m2.5", - name: "Minimax/Minimax-M2.5", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 204800, output: 128000 }, - }, - }, - }, - "ollama-cloud": { - id: "ollama-cloud", - env: ["OLLAMA_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://ollama.com/v1", - name: "Ollama Cloud", - doc: "https://docs.ollama.com/cloud", - models: { - "glm-5": { - id: "glm-5", - name: "glm-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 202752, output: 131072 }, - }, - "qwen3-coder:480b": { - id: "qwen3-coder:480b", - name: "qwen3-coder:480b", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2025-07-22", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 65536 }, - }, - "nemotron-3-nano:30b": { - id: "nemotron-3-nano:30b", - name: "nemotron-3-nano:30b", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: true, - release_date: "2025-12-15", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 1048576, output: 131072 }, - }, - "ministral-3:8b": { - id: "ministral-3:8b", - name: "ministral-3:8b", - family: "ministral", - attachment: true, - reasoning: false, - tool_call: true, - release_date: "2024-12-01", - last_updated: "2026-01-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 128000 }, - }, - "qwen3-coder-next": { - id: "qwen3-coder-next", - name: "qwen3-coder-next", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2026-02-02", - last_updated: "2026-02-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 65536 }, - }, - "gpt-oss:120b": { - id: "gpt-oss:120b", - name: "gpt-oss:120b", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - release_date: "2025-08-05", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 131072, output: 32768 }, - }, - "devstral-2:123b": { - id: "devstral-2:123b", - name: "devstral-2:123b", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2025-12-09", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 262144 }, - }, - "glm-4.6": { - id: "glm-4.6", - name: "glm-4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - release_date: "2025-09-29", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 202752, output: 131072 }, - }, - "qwen3-vl:235b-instruct": { - id: "qwen3-vl:235b-instruct", - name: "qwen3-vl:235b-instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - release_date: "2025-09-22", - last_updated: "2026-01-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 131072 }, - }, - "gemini-3-flash-preview": { - id: "gemini-3-flash-preview", - name: "gemini-3-flash-preview", - family: "gemini-flash", - attachment: false, - reasoning: true, - tool_call: true, - knowledge: "2025-01", - release_date: "2025-12-17", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 1048576, output: 65536 }, - }, - "minimax-m2.1": { - id: "minimax-m2.1", - name: "minimax-m2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - release_date: "2025-12-23", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 204800, output: 131072 }, - }, - "ministral-3:14b": { - id: "ministral-3:14b", - name: "ministral-3:14b", - family: "ministral", - attachment: true, - reasoning: false, - tool_call: true, - release_date: "2024-12-01", - last_updated: "2026-01-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 128000 }, - }, - "qwen3-next:80b": { - id: "qwen3-next:80b", - name: "qwen3-next:80b", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - release_date: "2025-09-15", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 32768 }, - }, - "kimi-k2:1t": { - id: "kimi-k2:1t", - name: "kimi-k2:1t", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - knowledge: "2024-10", - release_date: "2025-07-11", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 262144 }, - }, - "gemma3:12b": { - id: "gemma3:12b", - name: "gemma3:12b", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: false, - release_date: "2024-12-01", - last_updated: "2026-01-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 131072, output: 131072 }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "kimi-k2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 262144 }, - }, - "gpt-oss:20b": { - id: "gpt-oss:20b", - name: "gpt-oss:20b", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - release_date: "2025-08-05", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 131072, output: 32768 }, - }, - "deepseek-v3.2": { - id: "deepseek-v3.2", - name: "deepseek-v3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - release_date: "2025-06-15", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 163840, output: 65536 }, - }, - "glm-4.7": { - id: "glm-4.7", - name: "glm-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - release_date: "2025-12-22", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 202752, output: 131072 }, - }, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "kimi-k2-thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - knowledge: "2024-08", - release_date: "2025-11-06", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 262144 }, - }, - "ministral-3:3b": { - id: "ministral-3:3b", - name: "ministral-3:3b", - family: "ministral", - attachment: true, - reasoning: false, - tool_call: true, - release_date: "2024-10-22", - last_updated: "2026-01-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 128000 }, - }, - "qwen3.5:397b": { - id: "qwen3.5:397b", - name: "qwen3.5:397b", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - release_date: "2026-02-15", - last_updated: "2026-02-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 81920 }, - }, - "gemma3:27b": { - id: "gemma3:27b", - name: "gemma3:27b", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: false, - release_date: "2025-07-27", - last_updated: "2026-01-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 131072, output: 131072 }, - }, - "minimax-m2": { - id: "minimax-m2", - name: "minimax-m2", - family: "minimax", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2025-10-23", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 204800, output: 128000 }, - }, - "minimax-m2.5": { - id: "minimax-m2.5", - name: "minimax-m2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - knowledge: "2025-01", - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 204800, output: 131072 }, - }, - "devstral-small-2:24b": { - id: "devstral-small-2:24b", - name: "devstral-small-2:24b", - family: "devstral", - attachment: true, - reasoning: false, - tool_call: true, - release_date: "2025-12-09", - last_updated: "2026-01-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 262144 }, - }, - "nemotron-3-super": { - id: "nemotron-3-super", - name: "nemotron-3-super", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: true, - release_date: "2026-03-11", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 65536 }, - }, - "cogito-2.1:671b": { - id: "cogito-2.1:671b", - name: "cogito-2.1:671b", - family: "cogito", - attachment: false, - reasoning: true, - tool_call: true, - release_date: "2025-11-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 163840, output: 32000 }, - }, - "gemma3:4b": { - id: "gemma3:4b", - name: "gemma3:4b", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: false, - release_date: "2024-12-01", - last_updated: "2026-01-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 131072, output: 131072 }, - }, - "deepseek-v3.1:671b": { - id: "deepseek-v3.1:671b", - name: "deepseek-v3.1:671b", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - release_date: "2025-08-21", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 163840, output: 163840 }, - }, - "mistral-large-3:675b": { - id: "mistral-large-3:675b", - name: "mistral-large-3:675b", - family: "mistral-large", - attachment: true, - reasoning: false, - tool_call: true, - release_date: "2025-12-02", - last_updated: "2026-01-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 262144 }, - }, - "rnj-1:8b": { - id: "rnj-1:8b", - name: "rnj-1:8b", - family: "rnj", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2025-12-06", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 32768, output: 4096 }, - }, - "qwen3-vl:235b": { - id: "qwen3-vl:235b", - name: "qwen3-vl:235b", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - release_date: "2025-09-22", - last_updated: "2026-01-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 262144, output: 32768 }, - }, - }, - }, - scaleway: { - id: "scaleway", - env: ["SCALEWAY_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.scaleway.ai/v1", - name: "Scaleway", - doc: "https://www.scaleway.com/en/docs/generative-apis/", - models: { - "voxtral-small-24b-2507": { - id: "voxtral-small-24b-2507", - name: "Voxtral Small 24B 2507", - family: "voxtral", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-01", - last_updated: "2025-07-01", - modalities: { input: ["text", "audio"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.35 }, - limit: { context: 32000, output: 8192 }, - }, - "qwen3-235b-a22b-instruct-2507": { - id: "qwen3-235b-a22b-instruct-2507", - name: "Qwen3 235B A22B Instruct 2507", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-01", - last_updated: "2025-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.75, output: 2.25 }, - limit: { context: 260000, output: 8192 }, - }, - "llama-3.3-70b-instruct": { - id: "llama-3.3-70b-instruct", - name: "Llama-3.3-70B-Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.9, output: 0.9 }, - limit: { context: 100000, output: 4096 }, - }, - "mistral-small-3.2-24b-instruct-2506": { - id: "mistral-small-3.2-24b-instruct-2506", - name: "Mistral Small 3.2 24B Instruct (2506)", - family: "mistral-small", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-06-20", - last_updated: "2025-06-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.35 }, - limit: { context: 128000, output: 8192 }, - }, - "bge-multilingual-gemma2": { - id: "bge-multilingual-gemma2", - name: "BGE Multilingual Gemma2", - family: "gemma", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-07-26", - last_updated: "2025-06-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.13, output: 0 }, - limit: { context: 8191, output: 3072 }, - }, - "gpt-oss-120b": { - id: "gpt-oss-120b", - name: "GPT-OSS 120B", - family: "gpt-oss", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-01-01", - last_updated: "2024-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 8192 }, - }, - "deepseek-r1-distill-llama-70b": { - id: "deepseek-r1-distill-llama-70b", - name: "DeepSeek R1 Distill Llama 70B", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.9, output: 0.9 }, - limit: { context: 32000, output: 4096 }, - }, - "qwen3-coder-30b-a3b-instruct": { - id: "qwen3-coder-30b-a3b-instruct", - name: "Qwen3-Coder 30B-A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 128000, output: 8192 }, - }, - "whisper-large-v3": { - id: "whisper-large-v3", - name: "Whisper Large v3", - family: "whisper", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2023-09", - release_date: "2023-09-01", - last_updated: "2025-09-05", - modalities: { input: ["audio"], output: ["text"] }, - open_weights: true, - cost: { input: 0.003, output: 0 }, - limit: { context: 0, output: 4096 }, - }, - "llama-3.1-8b-instruct": { - id: "llama-3.1-8b-instruct", - name: "Llama 3.1 8B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 128000, output: 16384 }, - }, - "devstral-2-123b-instruct-2512": { - id: "devstral-2-123b-instruct-2512", - name: "Devstral 2 123B Instruct (2512)", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2026-01-07", - last_updated: "2026-01-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2 }, - limit: { context: 256000, output: 8192 }, - }, - "pixtral-12b-2409": { - id: "pixtral-12b-2409", - name: "Pixtral 12B 2409", - family: "pixtral", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 128000, output: 4096 }, - }, - "mistral-nemo-instruct-2407": { - id: "mistral-nemo-instruct-2407", - name: "Mistral Nemo Instruct 2407", - family: "mistral-nemo", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-07-25", - last_updated: "2024-07-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 128000, output: 8192 }, - }, - "gemma-3-27b-it": { - id: "gemma-3-27b-it", - name: "Gemma-3-27B-IT", - family: "gemma", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2024-12-01", - last_updated: "2025-09-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.5 }, - limit: { context: 40000, output: 8192 }, - }, - }, - }, - "cloudflare-ai-gateway": { - id: "cloudflare-ai-gateway", - env: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_GATEWAY_ID"], - npm: "ai-gateway-provider", - name: "Cloudflare AI Gateway", - doc: "https://developers.cloudflare.com/ai-gateway/", - models: { - "workers-ai/@cf/ibm-granite/granite-4.0-h-micro": { - id: "workers-ai/@cf/ibm-granite/granite-4.0-h-micro", - name: "IBM Granite 4.0 H Micro", - family: "granite", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.017, output: 0.11 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/baai/bge-small-en-v1.5": { - id: "workers-ai/@cf/baai/bge-small-en-v1.5", - name: "BGE Small EN v1.5", - family: "bge", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/baai/bge-large-en-v1.5": { - id: "workers-ai/@cf/baai/bge-large-en-v1.5", - name: "BGE Large EN v1.5", - family: "bge", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/baai/bge-reranker-base": { - id: "workers-ai/@cf/baai/bge-reranker-base", - name: "BGE Reranker Base", - family: "bge", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-09", - last_updated: "2025-04-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.0031, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/baai/bge-m3": { - id: "workers-ai/@cf/baai/bge-m3", - name: "BGE M3", - family: "bge", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.012, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/baai/bge-base-en-v1.5": { - id: "workers-ai/@cf/baai/bge-base-en-v1.5", - name: "BGE Base EN v1.5", - family: "bge", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.067, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/pfnet/plamo-embedding-1b": { - id: "workers-ai/@cf/pfnet/plamo-embedding-1b", - name: "PLaMo Embedding 1B", - family: "plamo", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.019, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/deepseek-ai/deepseek-r1-distill-qwen-32b": { - id: "workers-ai/@cf/deepseek-ai/deepseek-r1-distill-qwen-32b", - name: "DeepSeek R1 Distill Qwen 32B", - family: "deepseek-thinking", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 4.88 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/facebook/bart-large-cnn": { - id: "workers-ai/@cf/facebook/bart-large-cnn", - name: "BART Large CNN", - family: "bart", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-09", - last_updated: "2025-04-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/mistral/mistral-7b-instruct-v0.1": { - id: "workers-ai/@cf/mistral/mistral-7b-instruct-v0.1", - name: "Mistral 7B Instruct v0.1", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.11, output: 0.19 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/myshell-ai/melotts": { - id: "workers-ai/@cf/myshell-ai/melotts", - name: "MyShell MeloTTS", - family: "melotts", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/pipecat-ai/smart-turn-v2": { - id: "workers-ai/@cf/pipecat-ai/smart-turn-v2", - name: "Pipecat Smart Turn v2", - family: "smart-turn", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/google/gemma-3-12b-it": { - id: "workers-ai/@cf/google/gemma-3-12b-it", - name: "Gemma 3 12B IT", - family: "gemma", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-11", - last_updated: "2025-04-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.35, output: 0.56 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/qwen/qwq-32b": { - id: "workers-ai/@cf/qwen/qwq-32b", - name: "QwQ 32B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-11", - last_updated: "2025-04-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.66, output: 1 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/qwen/qwen3-30b-a3b-fp8": { - id: "workers-ai/@cf/qwen/qwen3-30b-a3b-fp8", - name: "Qwen3 30B A3B FP8", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.051, output: 0.34 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/qwen/qwen2.5-coder-32b-instruct": { - id: "workers-ai/@cf/qwen/qwen2.5-coder-32b-instruct", - name: "Qwen 2.5 Coder 32B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-11", - last_updated: "2025-04-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.66, output: 1 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/qwen/qwen3-embedding-0.6b": { - id: "workers-ai/@cf/qwen/qwen3-embedding-0.6b", - name: "Qwen3 Embedding 0.6B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.012, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/meta/llama-3.1-8b-instruct-fp8": { - id: "workers-ai/@cf/meta/llama-3.1-8b-instruct-fp8", - name: "Llama 3.1 8B Instruct FP8", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.29 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/meta/llama-3-8b-instruct-awq": { - id: "workers-ai/@cf/meta/llama-3-8b-instruct-awq", - name: "Llama 3 8B Instruct AWQ", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.12, output: 0.27 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/meta/llama-3.1-8b-instruct-awq": { - id: "workers-ai/@cf/meta/llama-3.1-8b-instruct-awq", - name: "Llama 3.1 8B Instruct AWQ", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.12, output: 0.27 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/meta/llama-4-scout-17b-16e-instruct": { - id: "workers-ai/@cf/meta/llama-4-scout-17b-16e-instruct", - name: "Llama 4 Scout 17B 16E Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.85 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/meta/llama-3.2-11b-vision-instruct": { - id: "workers-ai/@cf/meta/llama-3.2-11b-vision-instruct", - name: "Llama 3.2 11B Vision Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.049, output: 0.68 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/meta/llama-3.2-3b-instruct": { - id: "workers-ai/@cf/meta/llama-3.2-3b-instruct", - name: "Llama 3.2 3B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.051, output: 0.34 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/meta/llama-guard-3-8b": { - id: "workers-ai/@cf/meta/llama-guard-3-8b", - name: "Llama Guard 3 8B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.48, output: 0.03 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/meta/llama-3.2-1b-instruct": { - id: "workers-ai/@cf/meta/llama-3.2-1b-instruct", - name: "Llama 3.2 1B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.027, output: 0.2 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/meta/llama-3.3-70b-instruct-fp8-fast": { - id: "workers-ai/@cf/meta/llama-3.3-70b-instruct-fp8-fast", - name: "Llama 3.3 70B Instruct FP8 Fast", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 2.25 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/meta/llama-3.1-8b-instruct": { - id: "workers-ai/@cf/meta/llama-3.1-8b-instruct", - name: "Llama 3.1 8B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.28, output: 0.8299999999999998 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/meta/m2m100-1.2b": { - id: "workers-ai/@cf/meta/m2m100-1.2b", - name: "M2M100 1.2B", - family: "m2m", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.34, output: 0.34 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/meta/llama-2-7b-chat-fp16": { - id: "workers-ai/@cf/meta/llama-2-7b-chat-fp16", - name: "Llama 2 7B Chat FP16", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.56, output: 6.67 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/meta/llama-3-8b-instruct": { - id: "workers-ai/@cf/meta/llama-3-8b-instruct", - name: "Llama 3 8B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.28, output: 0.83 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/mistralai/mistral-small-3.1-24b-instruct": { - id: "workers-ai/@cf/mistralai/mistral-small-3.1-24b-instruct", - name: "Mistral Small 3.1 24B Instruct", - family: "mistral-small", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-11", - last_updated: "2025-04-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.35, output: 0.56 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/deepgram/aura-2-es": { - id: "workers-ai/@cf/deepgram/aura-2-es", - name: "Deepgram Aura 2 (ES)", - family: "aura", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/deepgram/nova-3": { - id: "workers-ai/@cf/deepgram/nova-3", - name: "Deepgram Nova 3", - family: "nova", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/deepgram/aura-2-en": { - id: "workers-ai/@cf/deepgram/aura-2-en", - name: "Deepgram Aura 2 (EN)", - family: "aura", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/openai/gpt-oss-120b": { - id: "workers-ai/@cf/openai/gpt-oss-120b", - name: "GPT OSS 120B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.35, output: 0.75 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/openai/gpt-oss-20b": { - id: "workers-ai/@cf/openai/gpt-oss-20b", - name: "GPT OSS 20B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.3 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/ai4bharat/indictrans2-en-indic-1B": { - id: "workers-ai/@cf/ai4bharat/indictrans2-en-indic-1B", - name: "IndicTrans2 EN-Indic 1B", - family: "indictrans", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.34, output: 0.34 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/huggingface/distilbert-sst-2-int8": { - id: "workers-ai/@cf/huggingface/distilbert-sst-2-int8", - name: "DistilBERT SST-2 INT8", - family: "distilbert", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.026, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "workers-ai/@cf/aisingapore/gemma-sea-lion-v4-27b-it": { - id: "workers-ai/@cf/aisingapore/gemma-sea-lion-v4-27b-it", - name: "Gemma SEA-LION v4 27B IT", - family: "gemma", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.35, output: 0.56 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5.3-codex": { - id: "openai/gpt-5.3-codex", - name: "GPT-5.3 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, input: 272000, output: 128000 }, - provider: { npm: "ai-gateway-provider" }, - }, - "openai/gpt-4o-mini": { - id: "openai/gpt-4o-mini", - name: "GPT-4o mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.08 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5.2-codex": { - id: "openai/gpt-5.2-codex", - name: "GPT-5.2 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, input: 272000, output: 128000 }, - provider: { npm: "ai-gateway-provider" }, - }, - "openai/o1": { - id: "openai/o1", - name: "o1", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2023-09", - release_date: "2024-12-05", - last_updated: "2024-12-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 60, cache_read: 7.5 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-5.1": { - id: "openai/gpt-5.1", - name: "GPT-5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/o3": { - id: "openai/o3", - name: "o3", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-3.5-turbo": { - id: "openai/gpt-3.5-turbo", - name: "GPT-3.5-turbo", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - knowledge: "2021-09-01", - release_date: "2023-03-01", - last_updated: "2023-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.5, cache_read: 1.25 }, - limit: { context: 16385, output: 4096 }, - }, - "openai/gpt-5.2": { - id: "openai/gpt-5.2", - name: "GPT-5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/o3-pro": { - id: "openai/o3-pro", - name: "o3-pro", - family: "o-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-06-10", - last_updated: "2025-06-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 20, output: 80 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4-turbo": { - id: "openai/gpt-4-turbo", - name: "GPT-4 Turbo", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - knowledge: "2023-12", - release_date: "2023-11-06", - last_updated: "2024-04-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 30 }, - limit: { context: 128000, output: 4096 }, - }, - "openai/o4-mini": { - id: "openai/o4-mini", - name: "o4-mini", - family: "o-mini", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.28 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-5.4": { - id: "openai/gpt-5.4", - name: "GPT-5.4", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 15, cache_read: 0.25 }, - limit: { context: 1050000, input: 922000, output: 128000 }, - provider: { npm: "ai-gateway-provider" }, - }, - "openai/gpt-5.1-codex": { - id: "openai/gpt-5.1-codex", - name: "GPT-5.1 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/o3-mini": { - id: "openai/o3-mini", - name: "o3-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05", - release_date: "2024-12-20", - last_updated: "2025-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.55 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4": { - id: "openai/gpt-4", - name: "GPT-4", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - knowledge: "2023-11", - release_date: "2023-11-06", - last_updated: "2024-04-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 30, output: 60 }, - limit: { context: 8192, output: 8192 }, - }, - "openai/gpt-4o": { - id: "openai/gpt-4o", - name: "GPT-4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-05-13", - last_updated: "2024-08-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10, cache_read: 1.25 }, - limit: { context: 128000, output: 16384 }, - }, - "anthropic/claude-3.5-sonnet": { - id: "anthropic/claude-3.5-sonnet", - name: "Claude Sonnet 3.5 v2", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04-30", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 8192 }, - }, - "anthropic/claude-opus-4-1": { - id: "anthropic/claude-opus-4-1", - name: "Claude Opus 4.1 (latest)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "anthropic/claude-3-sonnet": { - id: "anthropic/claude-3-sonnet", - name: "Claude Sonnet 3", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-08-31", - release_date: "2024-03-04", - last_updated: "2024-03-04", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 0.3 }, - limit: { context: 200000, output: 4096 }, - }, - "anthropic/claude-3-5-haiku": { - id: "anthropic/claude-3-5-haiku", - name: "Claude Haiku 3.5 (latest)", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07-31", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 }, - limit: { context: 200000, output: 8192 }, - }, - "anthropic/claude-opus-4-6": { - id: "anthropic/claude-opus-4-6", - name: "Claude Opus 4.6 (latest)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08-31", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 5, - output: 25, - cache_read: 0.5, - cache_write: 6.25, - context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 }, - }, - limit: { context: 1000000, output: 128000 }, - }, - "anthropic/claude-3-haiku": { - id: "anthropic/claude-3-haiku", - name: "Claude Haiku 3", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-08-31", - release_date: "2024-03-13", - last_updated: "2024-03-13", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1.25, cache_read: 0.03, cache_write: 0.3 }, - limit: { context: 200000, output: 4096 }, - }, - "anthropic/claude-sonnet-4-6": { - id: "anthropic/claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 1000000, output: 64000 }, - provider: { npm: "ai-gateway-provider" }, - }, - "anthropic/claude-3.5-haiku": { - id: "anthropic/claude-3.5-haiku", - name: "Claude Haiku 3.5 (latest)", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07-31", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 }, - limit: { context: 200000, output: 8192 }, - }, - "anthropic/claude-opus-4": { - id: "anthropic/claude-opus-4", - name: "Claude Opus 4 (latest)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "anthropic/claude-haiku-4-5": { - id: "anthropic/claude-haiku-4-5", - name: "Claude Haiku 4.5 (latest)", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-opus-4-5": { - id: "anthropic/claude-opus-4-5", - name: "Claude Opus 4.5 (latest)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-11-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-3-opus": { - id: "anthropic/claude-3-opus", - name: "Claude Opus 3", - family: "claude-opus", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-08-31", - release_date: "2024-02-29", - last_updated: "2024-02-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 4096 }, - }, - "anthropic/claude-sonnet-4": { - id: "anthropic/claude-sonnet-4", - name: "Claude Sonnet 4 (latest)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-sonnet-4-5": { - id: "anthropic/claude-sonnet-4-5", - name: "Claude Sonnet 4.5 (latest)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - }, - }, - "kuae-cloud-coding-plan": { - id: "kuae-cloud-coding-plan", - env: ["KUAE_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://coding-plan-endpoint.kuaecloud.net/v1", - name: "KUAE Cloud Coding Plan", - doc: "https://docs.mthreads.com/kuaecloud/kuaecloud-doc-online/coding_plan/", - models: { - "GLM-4.7": { - id: "GLM-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - }, - }, - upstage: { - id: "upstage", - env: ["UPSTAGE_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.upstage.ai/v1/solar", - name: "Upstage", - doc: "https://developers.upstage.ai/docs/apis/chat", - models: { - "solar-pro2": { - id: "solar-pro2", - name: "solar-pro2", - family: "solar-pro", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-05-20", - last_updated: "2025-05-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.25 }, - limit: { context: 65536, output: 8192 }, - }, - "solar-mini": { - id: "solar-mini", - name: "solar-mini", - family: "solar-mini", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2024-06-12", - last_updated: "2025-04-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 32768, output: 4096 }, - }, - "solar-pro3": { - id: "solar-pro3", - name: "solar-pro3", - family: "solar-pro", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.25 }, - limit: { context: 131072, output: 8192 }, - }, - }, - }, - inception: { - id: "inception", - env: ["INCEPTION_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.inceptionlabs.ai/v1/", - name: "Inception", - doc: "https://platform.inceptionlabs.ai/docs", - models: { - "mercury-2": { - id: "mercury-2", - name: "Mercury 2", - family: "mercury", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01-01", - release_date: "2026-02-24", - last_updated: "2026-02-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.75, cache_read: 0.025 }, - limit: { context: 128000, output: 50000 }, - }, - mercury: { - id: "mercury", - name: "Mercury", - family: "mercury", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2025-06-26", - last_updated: "2025-07-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1, cache_read: 0.25, cache_write: 1 }, - limit: { context: 128000, output: 16384 }, - }, - "mercury-edit": { - id: "mercury-edit", - name: "Mercury Edit", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2026-02-24", - last_updated: "2026-02-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.75, cache_read: 0.025 }, - limit: { context: 128000, output: 8192 }, - }, - "mercury-coder": { - id: "mercury-coder", - name: "Mercury Coder", - family: "mercury", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2025-02-26", - last_updated: "2025-07-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1, cache_read: 0.25, cache_write: 1 }, - limit: { context: 128000, output: 16384 }, - }, - }, - }, - submodel: { - id: "submodel", - env: ["SUBMODEL_INSTAGEN_ACCESS_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://llm.submodel.ai/v1", - name: "submodel", - doc: "https://submodel.gitbook.io", - models: { - "zai-org/GLM-4.5-Air": { - id: "zai-org/GLM-4.5-Air", - name: "GLM 4.5 Air", - family: "glm-air", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.5 }, - limit: { context: 131072, output: 131072 }, - }, - "zai-org/GLM-4.5-FP8": { - id: "zai-org/GLM-4.5-FP8", - name: "GLM 4.5 FP8", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 131072, output: 131072 }, - }, - "deepseek-ai/DeepSeek-R1-0528": { - id: "deepseek-ai/DeepSeek-R1-0528", - name: "DeepSeek R1 0528", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-23", - last_updated: "2025-08-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 2.15 }, - limit: { context: 75000, output: 163840 }, - }, - "deepseek-ai/DeepSeek-V3.1": { - id: "deepseek-ai/DeepSeek-V3.1", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-23", - last_updated: "2025-08-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 75000, output: 163840 }, - }, - "deepseek-ai/DeepSeek-V3-0324": { - id: "deepseek-ai/DeepSeek-V3-0324", - name: "DeepSeek V3 0324", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-08-23", - last_updated: "2025-08-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 75000, output: 163840 }, - }, - "Qwen/Qwen3-235B-A22B-Thinking-2507": { - id: "Qwen/Qwen3-235B-A22B-Thinking-2507", - name: "Qwen3 235B A22B Thinking 2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-23", - last_updated: "2025-08-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 262144, output: 131072 }, - }, - "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8": { - id: "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8", - name: "Qwen3 Coder 480B A35B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-08-23", - last_updated: "2025-08-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 262144, output: 262144 }, - }, - "Qwen/Qwen3-235B-A22B-Instruct-2507": { - id: "Qwen/Qwen3-235B-A22B-Instruct-2507", - name: "Qwen3 235B A22B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-08-23", - last_updated: "2025-08-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.3 }, - limit: { context: 262144, output: 131072 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-23", - last_updated: "2025-08-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.5 }, - limit: { context: 131072, output: 32768 }, - }, - }, - }, - "minimax-cn-coding-plan": { - id: "minimax-cn-coding-plan", - env: ["MINIMAX_API_KEY"], - npm: "@ai-sdk/anthropic", - api: "https://api.minimaxi.com/anthropic/v1", - name: "MiniMax Coding Plan (minimaxi.com)", - doc: "https://platform.minimaxi.com/docs/coding-plan/intro", - models: { - "MiniMax-M2.5": { - id: "MiniMax-M2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "MiniMax-M2": { - id: "MiniMax-M2", - name: "MiniMax-M2", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-10-27", - last_updated: "2025-10-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 196608, output: 128000 }, - }, - "MiniMax-M2.5-highspeed": { - id: "MiniMax-M2.5-highspeed", - name: "MiniMax-M2.5-highspeed", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-13", - last_updated: "2026-02-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "MiniMax-M2.1": { - id: "MiniMax-M2.1", - name: "MiniMax-M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 204800, output: 131072 }, - }, - }, - }, - "novita-ai": { - id: "novita-ai", - env: ["NOVITA_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.novita.ai/openai", - name: "NovitaAI", - doc: "https://novita.ai/docs/guides/introduction", - models: { - "zai-org/glm-5": { - id: "zai-org/glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2, cache_read: 0.2 }, - limit: { context: 202800, output: 131072 }, - }, - "zai-org/glm-4.5-air": { - id: "zai-org/glm-4.5-air", - name: "GLM 4.5 Air", - family: "glm-air", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-10-13", - last_updated: "2025-10-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.85 }, - limit: { context: 131072, output: 98304 }, - }, - "zai-org/glm-4.5": { - id: "zai-org/glm-4.5", - name: "GLM-4.5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.11 }, - limit: { context: 131072, output: 98304 }, - }, - "zai-org/glm-4.7-flash": { - id: "zai-org/glm-4.7-flash", - name: "GLM-4.7-Flash", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.4, cache_read: 0.01 }, - limit: { context: 200000, output: 128000 }, - }, - "zai-org/glm-4.6": { - id: "zai-org/glm-4.6", - name: "GLM 4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.2, cache_read: 0.11 }, - limit: { context: 204800, output: 131072 }, - }, - "zai-org/glm-4.7": { - id: "zai-org/glm-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.11 }, - limit: { context: 204800, output: 131072 }, - }, - "zai-org/autoglm-phone-9b-multilingual": { - id: "zai-org/autoglm-phone-9b-multilingual", - name: "AutoGLM-Phone-9B-Multilingual", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-12-10", - last_updated: "2025-12-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.035, output: 0.138 }, - limit: { context: 65536, output: 65536 }, - }, - "zai-org/glm-4.5v": { - id: "zai-org/glm-4.5v", - name: "GLM 4.5V", - family: "glmv", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-08-11", - last_updated: "2025-08-11", - modalities: { input: ["text", "video", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 1.8, cache_read: 0.11 }, - limit: { context: 65536, output: 16384 }, - }, - "zai-org/glm-4.6v": { - id: "zai-org/glm-4.6v", - name: "GLM 4.6V", - family: "glmv", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text", "video", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.9, cache_read: 0.055 }, - limit: { context: 131072, output: 32768 }, - }, - "microsoft/wizardlm-2-8x22b": { - id: "microsoft/wizardlm-2-8x22b", - name: "Wizardlm 2 8x22B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-04-24", - last_updated: "2024-04-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.62, output: 0.62 }, - limit: { context: 65535, output: 8000 }, - }, - "minimaxai/minimax-m1-80k": { - id: "minimaxai/minimax-m1-80k", - name: "MiniMax M1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.2 }, - limit: { context: 1000000, output: 40000 }, - }, - "skywork/r1v4-lite": { - id: "skywork/r1v4-lite", - name: "Skywork R1V4-Lite", - family: "skywork", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 262144, output: 65536 }, - }, - "gryphe/mythomax-l2-13b": { - id: "gryphe/mythomax-l2-13b", - name: "Mythomax L2 13B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-04-25", - last_updated: "2024-04-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.09 }, - limit: { context: 4096, output: 3200 }, - }, - "paddlepaddle/paddleocr-vl": { - id: "paddlepaddle/paddleocr-vl", - name: "PaddleOCR-VL", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-10-22", - last_updated: "2025-10-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.02 }, - limit: { context: 16384, output: 16384 }, - }, - "baichuan/baichuan-m2-32b": { - id: "baichuan/baichuan-m2-32b", - name: "baichuan-m2-32b", - family: "baichuan", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - knowledge: "2024-12", - release_date: "2025-08-13", - last_updated: "2025-08-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.07 }, - limit: { context: 131072, output: 131072 }, - }, - "kwaipilot/kat-coder-pro": { - id: "kwaipilot/kat-coder-pro", - name: "Kat Coder Pro", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01-05", - last_updated: "2026-01-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.06 }, - limit: { context: 256000, output: 128000 }, - }, - "kwaipilot/kat-coder": { - id: "kwaipilot/kat-coder", - name: "KAT-Coder-Pro V1(Free)", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 32000 }, - }, - "deepseek/deepseek-v3-turbo": { - id: "deepseek/deepseek-v3-turbo", - name: "DeepSeek V3 (Turbo)\t", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-03-05", - last_updated: "2025-03-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 1.3 }, - limit: { context: 64000, output: 16000 }, - }, - "deepseek/deepseek-prover-v2-671b": { - id: "deepseek/deepseek-prover-v2-671b", - name: "Deepseek Prover V2 671B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-30", - last_updated: "2025-04-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 2.5 }, - limit: { context: 160000, output: 160000 }, - }, - "deepseek/deepseek-r1-turbo": { - id: "deepseek/deepseek-r1-turbo", - name: "DeepSeek R1 (Turbo)\t", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-03-05", - last_updated: "2025-03-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 2.5 }, - limit: { context: 64000, output: 16000 }, - }, - "deepseek/deepseek-ocr-2": { - id: "deepseek/deepseek-ocr-2", - name: "deepseek/deepseek-ocr-2", - attachment: true, - reasoning: false, - tool_call: false, - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.03 }, - limit: { context: 8192, output: 8192 }, - }, - "deepseek/deepseek-v3.1": { - id: "deepseek/deepseek-v3.1", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 1, cache_read: 0.135 }, - limit: { context: 131072, output: 32768 }, - }, - "deepseek/deepseek-r1-0528": { - id: "deepseek/deepseek-r1-0528", - name: "DeepSeek R1 0528", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 2.5, cache_read: 0.35 }, - limit: { context: 163840, output: 32768 }, - }, - "deepseek/deepseek-r1-0528-qwen3-8b": { - id: "deepseek/deepseek-r1-0528-qwen3-8b", - name: "DeepSeek R1 0528 Qwen3 8B", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-05-29", - last_updated: "2025-05-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.09 }, - limit: { context: 128000, output: 32000 }, - }, - "deepseek/deepseek-r1-distill-llama-70b": { - id: "deepseek/deepseek-r1-distill-llama-70b", - name: "DeepSeek R1 Distill LLama 70B", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-01-27", - last_updated: "2025-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.8, output: 0.8 }, - limit: { context: 8192, output: 8192 }, - }, - "deepseek/deepseek-v3-0324": { - id: "deepseek/deepseek-v3-0324", - name: "DeepSeek V3 0324", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-03-25", - last_updated: "2025-03-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 1.12, cache_read: 0.135 }, - limit: { context: 163840, output: 163840 }, - }, - "deepseek/deepseek-v3.1-terminus": { - id: "deepseek/deepseek-v3.1-terminus", - name: "Deepseek V3.1 Terminus", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-22", - last_updated: "2025-09-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 1, cache_read: 0.135 }, - limit: { context: 131072, output: 32768 }, - }, - "deepseek/deepseek-v3.2": { - id: "deepseek/deepseek-v3.2", - name: "Deepseek V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.269, output: 0.4, cache_read: 0.1345 }, - limit: { context: 163840, output: 65536 }, - }, - "deepseek/deepseek-ocr": { - id: "deepseek/deepseek-ocr", - name: "DeepSeek-OCR", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-10-24", - last_updated: "2025-10-24", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.03 }, - limit: { context: 8192, output: 8192 }, - }, - "deepseek/deepseek-v3.2-exp": { - id: "deepseek/deepseek-v3.2-exp", - name: "Deepseek V3.2 Exp", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 0.41 }, - limit: { context: 163840, output: 65536 }, - }, - "moonshotai/kimi-k2-instruct": { - id: "moonshotai/kimi-k2-instruct", - name: "Kimi K2 Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-11", - last_updated: "2025-07-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.57, output: 2.3 }, - limit: { context: 131072, output: 131072 }, - }, - "moonshotai/kimi-k2-0905": { - id: "moonshotai/kimi-k2-0905", - name: "Kimi K2 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5 }, - limit: { context: 262144, output: 262144 }, - }, - "moonshotai/kimi-k2.5": { - id: "moonshotai/kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3, cache_read: 0.1 }, - limit: { context: 262144, output: 262144 }, - }, - "moonshotai/kimi-k2-thinking": { - id: "moonshotai/kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-11-07", - last_updated: "2025-11-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5 }, - limit: { context: 262144, output: 262144 }, - }, - "baidu/ernie-4.5-vl-28b-a3b-thinking": { - id: "baidu/ernie-4.5-vl-28b-a3b-thinking", - name: "ERNIE-4.5-VL-28B-A3B-Thinking", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-11-26", - last_updated: "2025-11-26", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.39, output: 0.39 }, - limit: { context: 131072, output: 65536 }, - }, - "baidu/ernie-4.5-vl-424b-a47b": { - id: "baidu/ernie-4.5-vl-424b-a47b", - name: "ERNIE 4.5 VL 424B A47B", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-06-30", - last_updated: "2025-06-30", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.42, output: 1.25 }, - limit: { context: 123000, output: 16000 }, - }, - "baidu/ernie-4.5-vl-28b-a3b": { - id: "baidu/ernie-4.5-vl-28b-a3b", - name: "ERNIE 4.5 VL 28B A3B", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-06-30", - last_updated: "2025-06-30", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 1.4, output: 5.6 }, - limit: { context: 30000, output: 8000 }, - }, - "baidu/ernie-4.5-300b-a47b-paddle": { - id: "baidu/ernie-4.5-300b-a47b-paddle", - name: "ERNIE 4.5 300B A47B", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-06-30", - last_updated: "2025-06-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 1.1 }, - limit: { context: 123000, output: 12000 }, - }, - "baidu/ernie-4.5-21B-a3b": { - id: "baidu/ernie-4.5-21B-a3b", - name: "ERNIE 4.5 21B A3B", - family: "ernie", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-06-30", - last_updated: "2025-06-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.28 }, - limit: { context: 120000, output: 8000 }, - }, - "baidu/ernie-4.5-21B-a3b-thinking": { - id: "baidu/ernie-4.5-21B-a3b-thinking", - name: "ERNIE-4.5-21B-A3B-Thinking", - family: "ernie", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-03", - release_date: "2025-09-19", - last_updated: "2025-09-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.28 }, - limit: { context: 131072, output: 65536 }, - }, - "google/gemma-3-27b-it": { - id: "google/gemma-3-27b-it", - name: "Gemma 3 27B", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-03-25", - last_updated: "2025-03-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.119, output: 0.2 }, - limit: { context: 98304, output: 16384 }, - }, - "qwen/qwen3-4b-fp8": { - id: "qwen/qwen3-4b-fp8", - name: "Qwen3 4B", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-04-29", - last_updated: "2025-04-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.03 }, - limit: { context: 128000, output: 20000 }, - }, - "qwen/qwen3-235b-a22b-instruct-2507": { - id: "qwen/qwen3-235b-a22b-instruct-2507", - name: "Qwen3 235B A22B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-22", - last_updated: "2025-07-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.58 }, - limit: { context: 131072, output: 16384 }, - }, - "qwen/qwen3-32b-fp8": { - id: "qwen/qwen3-32b-fp8", - name: "Qwen3 32B", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-04-29", - last_updated: "2025-04-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.45 }, - limit: { context: 40960, output: 20000 }, - }, - "qwen/qwen3-next-80b-a3b-thinking": { - id: "qwen/qwen3-next-80b-a3b-thinking", - name: "Qwen3 Next 80B A3B Thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-10", - last_updated: "2025-09-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 1.5 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen3-coder-480b-a35b-instruct": { - id: "qwen/qwen3-coder-480b-a35b-instruct", - name: "Qwen3 Coder 480B A35B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.3 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen/qwen3-30b-a3b-fp8": { - id: "qwen/qwen3-30b-a3b-fp8", - name: "Qwen3 30B A3B", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-04-29", - last_updated: "2025-04-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.45 }, - limit: { context: 40960, output: 20000 }, - }, - "qwen/qwen3-coder-next": { - id: "qwen/qwen3-coder-next", - name: "Qwen3 Coder Next", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-03", - last_updated: "2026-02-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 1.5 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen/qwen3.5-397b-a17b": { - id: "qwen/qwen3.5-397b-a17b", - name: "Qwen3.5-397B-A17B", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3.6 }, - limit: { context: 262144, output: 64000 }, - }, - "qwen/qwen2.5-vl-72b-instruct": { - id: "qwen/qwen2.5-vl-72b-instruct", - name: "Qwen2.5 VL 72B Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-03-25", - last_updated: "2025-03-25", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.8, output: 0.8 }, - limit: { context: 32768, output: 32768 }, - }, - "qwen/qwen3-coder-30b-a3b-instruct": { - id: "qwen/qwen3-coder-30b-a3b-instruct", - name: "Qwen3 Coder 30b A3B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-09", - last_updated: "2025-10-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.27 }, - limit: { context: 160000, output: 32768 }, - }, - "qwen/qwen3-vl-235b-a22b-instruct": { - id: "qwen/qwen3-vl-235b-a22b-instruct", - name: "Qwen3 VL 235B A22B Instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-24", - last_updated: "2025-09-24", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.5 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen-mt-plus": { - id: "qwen/qwen-mt-plus", - name: "Qwen MT Plus", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-09-03", - last_updated: "2025-09-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 0.75 }, - limit: { context: 16384, output: 8192 }, - }, - "qwen/qwen3-omni-30b-a3b-instruct": { - id: "qwen/qwen3-omni-30b-a3b-instruct", - name: "Qwen3 Omni 30B A3B Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-09-24", - last_updated: "2025-09-24", - modalities: { input: ["text", "video", "audio", "image"], output: ["text", "audio"] }, - open_weights: true, - cost: { input: 0.25, output: 0.97, input_audio: 2.2, output_audio: 1.788 }, - limit: { context: 65536, output: 16384 }, - }, - "qwen/qwen-2.5-72b-instruct": { - id: "qwen/qwen-2.5-72b-instruct", - name: "Qwen 2.5 72B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-10-15", - last_updated: "2024-10-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.38, output: 0.4 }, - limit: { context: 32000, output: 8192 }, - }, - "qwen/qwen3-vl-30b-a3b-thinking": { - id: "qwen/qwen3-vl-30b-a3b-thinking", - name: "qwen/qwen3-vl-30b-a3b-thinking", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-11", - last_updated: "2025-10-11", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 1 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen3-vl-235b-a22b-thinking": { - id: "qwen/qwen3-vl-235b-a22b-thinking", - name: "Qwen3 VL 235B A22B Thinking", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-09-24", - last_updated: "2025-09-24", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.98, output: 3.95 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen3-235b-a22b-thinking-2507": { - id: "qwen/qwen3-235b-a22b-thinking-2507", - name: "Qwen3 235B A22b Thinking 2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-25", - last_updated: "2025-07-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 3 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen2.5-7b-instruct": { - id: "qwen/qwen2.5-7b-instruct", - name: "Qwen2.5 7B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.07 }, - limit: { context: 32000, output: 32000 }, - }, - "qwen/qwen3-vl-30b-a3b-instruct": { - id: "qwen/qwen3-vl-30b-a3b-instruct", - name: "qwen/qwen3-vl-30b-a3b-instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-11", - last_updated: "2025-10-11", - modalities: { input: ["text", "video", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.7 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen3-next-80b-a3b-instruct": { - id: "qwen/qwen3-next-80b-a3b-instruct", - name: "Qwen3 Next 80B A3B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-10", - last_updated: "2025-09-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 1.5 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen3-235b-a22b-fp8": { - id: "qwen/qwen3-235b-a22b-fp8", - name: "Qwen3 235B A22B", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-04-29", - last_updated: "2025-04-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 40960, output: 20000 }, - }, - "qwen/qwen3-vl-8b-instruct": { - id: "qwen/qwen3-vl-8b-instruct", - name: "qwen/qwen3-vl-8b-instruct", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-17", - last_updated: "2025-10-17", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.08, output: 0.5 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen3-max": { - id: "qwen/qwen3-max", - name: "Qwen3 Max", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-24", - last_updated: "2025-09-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.11, output: 8.45 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen/qwen3-8b-fp8": { - id: "qwen/qwen3-8b-fp8", - name: "Qwen3 8B", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-04-29", - last_updated: "2025-04-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.035, output: 0.138 }, - limit: { context: 128000, output: 20000 }, - }, - "qwen/qwen3-omni-30b-a3b-thinking": { - id: "qwen/qwen3-omni-30b-a3b-thinking", - name: "Qwen3 Omni 30B A3B Thinking", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-24", - last_updated: "2025-09-24", - modalities: { input: ["text", "audio", "video", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 0.97, input_audio: 2.2, output_audio: 1.788 }, - limit: { context: 65536, output: 16384 }, - }, - "meta-llama/llama-3.3-70b-instruct": { - id: "meta-llama/llama-3.3-70b-instruct", - name: "Llama 3.3 70B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-07", - last_updated: "2024-12-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.135, output: 0.4 }, - limit: { context: 131072, output: 120000 }, - }, - "meta-llama/llama-4-scout-17b-16e-instruct": { - id: "meta-llama/llama-4-scout-17b-16e-instruct", - name: "Llama 4 Scout Instruct", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-06", - last_updated: "2025-04-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.18, output: 0.59 }, - limit: { context: 131072, output: 131072 }, - }, - "meta-llama/llama-3-70b-instruct": { - id: "meta-llama/llama-3-70b-instruct", - name: "Llama3 70B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2024-04-25", - last_updated: "2024-04-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.51, output: 0.74 }, - limit: { context: 8192, output: 8000 }, - }, - "meta-llama/llama-3.1-8b-instruct": { - id: "meta-llama/llama-3.1-8b-instruct", - name: "Llama 3.1 8B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-07-24", - last_updated: "2024-07-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.05 }, - limit: { context: 16384, output: 16384 }, - }, - "meta-llama/llama-3-8b-instruct": { - id: "meta-llama/llama-3-8b-instruct", - name: "Llama 3 8B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-04-25", - last_updated: "2024-04-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.04 }, - limit: { context: 8192, output: 8192 }, - }, - "meta-llama/llama-4-maverick-17b-128e-instruct-fp8": { - id: "meta-llama/llama-4-maverick-17b-128e-instruct-fp8", - name: "Llama 4 Maverick Instruct", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-06", - last_updated: "2025-04-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 0.85 }, - limit: { context: 1048576, output: 8192 }, - }, - "mistralai/mistral-nemo": { - id: "mistralai/mistral-nemo", - name: "Mistral Nemo", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2024-07-30", - last_updated: "2024-07-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.17 }, - limit: { context: 60288, output: 16000 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "OpenAI GPT OSS 120B", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-06", - last_updated: "2025-08-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.25 }, - limit: { context: 131072, output: 32768 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "OpenAI: GPT OSS 20B", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-08-06", - last_updated: "2025-08-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.15 }, - limit: { context: 131072, output: 32768 }, - }, - "minimax/minimax-m2.1": { - id: "minimax/minimax-m2.1", - name: "Minimax M2.1", - family: "minimax", - attachment: false, - reasoning: false, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.03 }, - limit: { context: 204800, output: 131072 }, - }, - "minimax/minimax-m2": { - id: "minimax/minimax-m2", - name: "MiniMax-M2", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2025-10-27", - last_updated: "2025-10-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.03 }, - limit: { context: 204800, output: 131072 }, - }, - "minimax/minimax-m2.5": { - id: "minimax/minimax-m2.5", - name: "MiniMax M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2, cache_read: 0.03 }, - limit: { context: 204800, output: 131100 }, - }, - "sao10k/l3-70b-euryale-v2.1": { - id: "sao10k/l3-70b-euryale-v2.1", - name: "L3 70B Euryale V2.1\t", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-06-18", - last_updated: "2024-06-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.48, output: 1.48 }, - limit: { context: 8192, output: 8192 }, - }, - "sao10k/l31-70b-euryale-v2.2": { - id: "sao10k/l31-70b-euryale-v2.2", - name: "L31 70B Euryale V2.2", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-09-19", - last_updated: "2024-09-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.48, output: 1.48 }, - limit: { context: 8192, output: 8192 }, - }, - "sao10k/l3-8b-lunaris": { - id: "sao10k/l3-8b-lunaris", - name: "Sao10k L3 8B Lunaris\t", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2024-11-28", - last_updated: "2024-11-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.05 }, - limit: { context: 8192, output: 8192 }, - }, - "sao10k/L3-8B-Stheno-v3.2": { - id: "sao10k/L3-8B-Stheno-v3.2", - name: "L3 8B Stheno V3.2", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-11-29", - last_updated: "2024-11-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.05 }, - limit: { context: 8192, output: 32000 }, - }, - "xiaomimimo/mimo-v2-flash": { - id: "xiaomimimo/mimo-v2-flash", - name: "XiaomiMiMo/MiMo-V2-Flash", - family: "mimo", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-12-19", - last_updated: "2025-12-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3, cache_read: 0.3 }, - limit: { context: 262144, output: 32000 }, - }, - "nousresearch/hermes-2-pro-llama-3-8b": { - id: "nousresearch/hermes-2-pro-llama-3-8b", - name: "Hermes 2 Pro Llama 3 8B", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2024-06-27", - last_updated: "2024-06-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.14, output: 0.14 }, - limit: { context: 8192, output: 8192 }, - }, - }, - }, - opencode: { - id: "opencode", - env: ["OPENCODE_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://opencode.ai/zen/v1", - name: "OpenCode Zen", - doc: "https://opencode.ai/docs/zen", - models: { - "gpt-5.3-codex": { - id: "gpt-5.3-codex", - name: "GPT-5.3 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-02-24", - last_updated: "2026-02-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, input: 272000, output: 128000 }, - provider: { npm: "@ai-sdk/openai" }, - }, - "kimi-k2": { - id: "kimi-k2", - name: "Kimi K2", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2.5, cache_read: 0.4 }, - limit: { context: 262144, output: 262144 }, - status: "deprecated", - }, - "gpt-5-codex": { - id: "gpt-5-codex", - name: "GPT-5 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.07, output: 8.5, cache_read: 0.107 }, - limit: { context: 400000, input: 272000, output: 128000 }, - provider: { npm: "@ai-sdk/openai" }, - }, - "gemini-3.1-pro": { - id: "gemini-3.1-pro", - name: "Gemini 3.1 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-19", - last_updated: "2026-02-19", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, context_over_200k: { input: 4, output: 18, cache_read: 0.4 } }, - limit: { context: 1048576, output: 65536 }, - provider: { npm: "@ai-sdk/google" }, - }, - "trinity-large-preview-free": { - id: "trinity-large-preview-free", - name: "Trinity Large Preview", - family: "trinity", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2026-01-28", - last_updated: "2026-01-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 131072 }, - status: "deprecated", - }, - "glm-5": { - id: "glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2, cache_read: 0.2 }, - limit: { context: 204800, output: 131072 }, - }, - "gpt-5.1-codex-max": { - id: "gpt-5.1-codex-max", - name: "GPT-5.1 Codex Max", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, input: 272000, output: 128000 }, - provider: { npm: "@ai-sdk/openai" }, - }, - "kimi-k2.5-free": { - id: "kimi-k2.5-free", - name: "Kimi K2.5 Free", - family: "kimi-free", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-10", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0 }, - limit: { context: 262144, output: 262144 }, - status: "deprecated", - }, - "claude-opus-4-1": { - id: "claude-opus-4-1", - name: "Claude Opus 4.1", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - provider: { npm: "@ai-sdk/anthropic" }, - }, - "grok-code": { - id: "grok-code", - name: "Grok Code Fast 1", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-20", - last_updated: "2025-08-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 256000, output: 256000 }, - status: "deprecated", - }, - "nemotron-3-super-free": { - id: "nemotron-3-super-free", - name: "Nemotron 3 Super Free", - family: "nemotron-free", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2026-02", - release_date: "2026-03-11", - last_updated: "2026-03-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0 }, - limit: { context: 1000000, output: 128000 }, - }, - "claude-3-5-haiku": { - id: "claude-3-5-haiku", - name: "Claude Haiku 3.5", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07-31", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 }, - limit: { context: 200000, output: 8192 }, - provider: { npm: "@ai-sdk/anthropic" }, - }, - "gpt-5.2-codex": { - id: "gpt-5.2-codex", - name: "GPT-5.2 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-01-14", - last_updated: "2026-01-14", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, input: 272000, output: 128000 }, - provider: { npm: "@ai-sdk/openai" }, - }, - "claude-opus-4-6": { - id: "claude-opus-4-6", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08-31", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 1000000, output: 128000 }, - provider: { npm: "@ai-sdk/anthropic" }, - }, - "mimo-v2-flash-free": { - id: "mimo-v2-flash-free", - name: "MiMo V2 Flash Free", - family: "mimo-flash-free", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-12", - release_date: "2025-12-16", - last_updated: "2025-12-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0 }, - limit: { context: 262144, output: 65536 }, - }, - "gemini-3-flash": { - id: "gemini-3-flash", - name: "Gemini 3 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 3, cache_read: 0.05 }, - limit: { context: 1048576, output: 65536 }, - provider: { npm: "@ai-sdk/google" }, - }, - "claude-sonnet-4-6": { - id: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 1000000, output: 64000 }, - provider: { npm: "@ai-sdk/anthropic" }, - }, - "gpt-5.1": { - id: "gpt-5.1", - name: "GPT-5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.07, output: 8.5, cache_read: 0.107 }, - limit: { context: 400000, input: 272000, output: 128000 }, - provider: { npm: "@ai-sdk/openai" }, - }, - "gpt-5.3-codex-spark": { - id: "gpt-5.3-codex-spark", - name: "GPT-5.3 Codex Spark", - family: "gpt-codex-spark", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 128000, input: 128000, output: 128000 }, - provider: { npm: "@ai-sdk/openai" }, - }, - "qwen3-coder": { - id: "qwen3-coder", - name: "Qwen3 Coder", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.45, output: 1.8 }, - limit: { context: 262144, output: 65536 }, - status: "deprecated", - }, - "glm-4.6": { - id: "glm-4.6", - name: "GLM-4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.1 }, - limit: { context: 204800, output: 131072 }, - }, - "minimax-m2.1": { - id: "minimax-m2.1", - name: "MiniMax M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.1 }, - limit: { context: 204800, output: 131072 }, - }, - "gpt-5.1-codex-mini": { - id: "gpt-5.1-codex-mini", - name: "GPT-5.1 Codex Mini", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.025 }, - limit: { context: 400000, input: 272000, output: 128000 }, - provider: { npm: "@ai-sdk/openai" }, - }, - "gpt-5.2": { - id: "gpt-5.2", - name: "GPT-5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, input: 272000, output: 128000 }, - provider: { npm: "@ai-sdk/openai" }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-10", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3, cache_read: 0.08 }, - limit: { context: 262144, output: 65536 }, - }, - "minimax-m2.1-free": { - id: "minimax-m2.1-free", - name: "MiniMax M2.1 Free", - family: "minimax-free", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0 }, - limit: { context: 204800, output: 131072 }, - status: "deprecated", - provider: { npm: "@ai-sdk/anthropic" }, - }, - "gpt-5": { - id: "gpt-5", - name: "GPT-5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.07, output: 8.5, cache_read: 0.107 }, - limit: { context: 400000, input: 272000, output: 128000 }, - provider: { npm: "@ai-sdk/openai" }, - }, - "glm-4.7": { - id: "glm-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.1 }, - limit: { context: 204800, output: 131072 }, - }, - "glm-5-free": { - id: "glm-5-free", - name: "GLM-5 Free", - family: "glm-free", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0 }, - limit: { context: 204800, output: 131072 }, - status: "deprecated", - }, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2.5, cache_read: 0.4 }, - limit: { context: 262144, output: 262144 }, - status: "deprecated", - }, - "gpt-5.4": { - id: "gpt-5.4", - name: "GPT-5.4", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 15, cache_read: 0.25 }, - limit: { context: 1050000, input: 922000, output: 128000 }, - provider: { npm: "@ai-sdk/openai" }, - }, - "gpt-5.4-pro": { - id: "gpt-5.4-pro", - name: "GPT-5.4 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 30, output: 180, cache_read: 30 }, - limit: { context: 1050000, input: 922000, output: 128000 }, - provider: { npm: "@ai-sdk/openai" }, - }, - "claude-haiku-4-5": { - id: "claude-haiku-4-5", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - provider: { npm: "@ai-sdk/anthropic" }, - }, - "gpt-5.1-codex": { - id: "gpt-5.1-codex", - name: "GPT-5.1 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.07, output: 8.5, cache_read: 0.107 }, - limit: { context: 400000, input: 272000, output: 128000 }, - provider: { npm: "@ai-sdk/openai" }, - }, - "big-pickle": { - id: "big-pickle", - name: "Big Pickle", - family: "big-pickle", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-10-17", - last_updated: "2025-10-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 200000, output: 128000 }, - provider: { npm: "@ai-sdk/anthropic" }, - }, - "minimax-m2.5-free": { - id: "minimax-m2.5-free", - name: "MiniMax M2.5 Free", - family: "minimax-free", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0 }, - limit: { context: 204800, output: 131072 }, - provider: { npm: "@ai-sdk/anthropic" }, - }, - "minimax-m2.5": { - id: "minimax-m2.5", - name: "MiniMax M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.06 }, - limit: { context: 204800, output: 131072 }, - }, - "claude-opus-4-5": { - id: "claude-opus-4-5", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-11-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - provider: { npm: "@ai-sdk/anthropic" }, - }, - "claude-sonnet-4": { - id: "claude-sonnet-4", - name: "Claude Sonnet 4", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 1000000, output: 64000 }, - provider: { npm: "@ai-sdk/anthropic" }, - }, - "glm-4.7-free": { - id: "glm-4.7-free", - name: "GLM-4.7 Free", - family: "glm-free", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0 }, - limit: { context: 204800, output: 131072 }, - status: "deprecated", - }, - "gemini-3-pro": { - id: "gemini-3-pro", - name: "Gemini 3 Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, context_over_200k: { input: 4, output: 18, cache_read: 0.4 } }, - limit: { context: 1048576, output: 65536 }, - provider: { npm: "@ai-sdk/google" }, - }, - "claude-sonnet-4-5": { - id: "claude-sonnet-4-5", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 1000000, output: 64000 }, - provider: { npm: "@ai-sdk/anthropic" }, - }, - "gpt-5-nano": { - id: "gpt-5-nano", - name: "GPT-5 Nano", - family: "gpt-nano", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0 }, - limit: { context: 400000, input: 272000, output: 128000 }, - provider: { npm: "@ai-sdk/openai" }, - }, - }, - }, - poe: { - id: "poe", - env: ["POE_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.poe.com/v1", - name: "Poe", - doc: "https://creator.poe.com/docs/external-applications/openai-compatible-api", - models: { - "stabilityai/stablediffusionxl": { - id: "stabilityai/stablediffusionxl", - name: "StableDiffusionXL", - family: "stable-diffusion", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2023-07-09", - last_updated: "2023-07-09", - modalities: { input: ["text", "image"], output: ["image"] }, - open_weights: false, - limit: { context: 200, output: 0 }, - }, - "ideogramai/ideogram-v2": { - id: "ideogramai/ideogram-v2", - name: "Ideogram-v2", - family: "ideogram", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-08-21", - last_updated: "2024-08-21", - modalities: { input: ["text", "image"], output: ["image"] }, - open_weights: false, - limit: { context: 150, output: 0 }, - }, - "ideogramai/ideogram": { - id: "ideogramai/ideogram", - name: "Ideogram", - family: "ideogram", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-04-03", - last_updated: "2024-04-03", - modalities: { input: ["text", "image"], output: ["image"] }, - open_weights: false, - limit: { context: 150, output: 0 }, - }, - "ideogramai/ideogram-v2a-turbo": { - id: "ideogramai/ideogram-v2a-turbo", - name: "Ideogram-v2a-Turbo", - family: "ideogram", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-02-27", - last_updated: "2025-02-27", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 150, output: 0 }, - }, - "ideogramai/ideogram-v2a": { - id: "ideogramai/ideogram-v2a", - name: "Ideogram-v2a", - family: "ideogram", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-02-27", - last_updated: "2025-02-27", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 150, output: 0 }, - }, - "novita/glm-4.7-flash": { - id: "novita/glm-4.7-flash", - name: "glm-4.7-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 200000, output: 65500 }, - }, - "novita/glm-4.7-n": { - id: "novita/glm-4.7-n", - name: "glm-4.7-n", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 205000, output: 131072 }, - }, - "novita/glm-4.6": { - id: "novita/glm-4.6", - name: "GLM-4.6", - family: "glm", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "novita/minimax-m2.1": { - id: "novita/minimax-m2.1", - name: "minimax-m2.1", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-12-26", - last_updated: "2025-12-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 205000, output: 131072 }, - }, - "novita/kimi-k2.5": { - id: "novita/kimi-k2.5", - name: "kimi-k2.5", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 262144 }, - }, - "novita/glm-4.7": { - id: "novita/glm-4.7", - name: "glm-4.7", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 205000, output: 131072 }, - }, - "novita/kimi-k2-thinking": { - id: "novita/kimi-k2-thinking", - name: "kimi-k2-thinking", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-11-07", - last_updated: "2025-11-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 0 }, - }, - "novita/glm-4.6v": { - id: "novita/glm-4.6v", - name: "glm-4.6v", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-12-09", - last_updated: "2025-12-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 131000, output: 32768 }, - }, - "google/gemini-3.1-pro": { - id: "google/gemini-3.1-pro", - name: "Gemini-3.1-Pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2026-02-19", - last_updated: "2026-02-19", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/lyria": { - id: "google/lyria", - name: "Lyria", - family: "lyria", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-06-04", - last_updated: "2025-06-04", - modalities: { input: ["text"], output: ["audio"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "google/gemini-3-flash": { - id: "google/gemini-3-flash", - name: "Gemini-3-Flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-10-07", - last_updated: "2025-10-07", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2.4, cache_read: 0.04 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/imagen-3": { - id: "google/imagen-3", - name: "Imagen-3", - family: "imagen", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-10-15", - last_updated: "2024-10-15", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 480, output: 0 }, - }, - "google/gemini-2.5-flash": { - id: "google/gemini-2.5-flash", - name: "Gemini-2.5-Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-04-26", - last_updated: "2025-04-26", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.21, output: 1.8, cache_read: 0.021 }, - limit: { context: 1065535, output: 65535 }, - }, - "google/veo-3.1": { - id: "google/veo-3.1", - name: "Veo-3.1", - family: "veo", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text"], output: ["video"] }, - open_weights: false, - limit: { context: 480, output: 0 }, - }, - "google/imagen-3-fast": { - id: "google/imagen-3-fast", - name: "Imagen-3-Fast", - family: "imagen", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-10-17", - last_updated: "2024-10-17", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 480, output: 0 }, - }, - "google/nano-banana-pro": { - id: "google/nano-banana-pro", - name: "Nano-Banana-Pro", - family: "nano-banana", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text", "image"], output: ["image"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2 }, - limit: { context: 65536, output: 0 }, - }, - "google/veo-2": { - id: "google/veo-2", - name: "Veo-2", - family: "veo", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-12-02", - last_updated: "2024-12-02", - modalities: { input: ["text"], output: ["video"] }, - open_weights: false, - limit: { context: 480, output: 0 }, - }, - "google/imagen-4-ultra": { - id: "google/imagen-4-ultra", - name: "Imagen-4-Ultra", - family: "imagen", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-05-24", - last_updated: "2025-05-24", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 480, output: 0 }, - }, - "google/gemini-2.5-flash-lite": { - id: "google/gemini-2.5-flash-lite", - name: "Gemini-2.5-Flash-Lite", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-06-19", - last_updated: "2025-06-19", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.28 }, - limit: { context: 1024000, output: 64000 }, - }, - "google/nano-banana": { - id: "google/nano-banana", - name: "Nano-Banana", - family: "nano-banana", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text", "image"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 0.21, output: 1.8, cache_read: 0.021 }, - limit: { context: 65536, output: 0 }, - }, - "google/veo-3.1-fast": { - id: "google/veo-3.1-fast", - name: "Veo-3.1-Fast", - family: "veo", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image"], output: ["video"] }, - open_weights: false, - limit: { context: 480, output: 0 }, - }, - "google/gemini-deep-research": { - id: "google/gemini-deep-research", - name: "gemini-deep-research", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 1.6, output: 9.6 }, - limit: { context: 1048576, output: 0 }, - }, - "google/veo-3": { - id: "google/veo-3", - name: "Veo-3", - family: "veo", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-05-21", - last_updated: "2025-05-21", - modalities: { input: ["text"], output: ["video"] }, - open_weights: false, - limit: { context: 480, output: 0 }, - }, - "google/imagen-4": { - id: "google/imagen-4", - name: "Imagen-4", - family: "imagen", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 480, output: 0 }, - }, - "google/gemini-2.0-flash-lite": { - id: "google/gemini-2.0-flash-lite", - name: "Gemini-2.0-Flash-Lite", - family: "gemini-flash-lite", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-02-05", - last_updated: "2025-02-05", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.052, output: 0.21 }, - limit: { context: 990000, output: 8192 }, - }, - "google/gemini-3.1-flash-lite": { - id: "google/gemini-3.1-flash-lite", - name: "Gemini-3.1-Flash-Lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2026-02-18", - last_updated: "2026-02-18", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1.5 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-3-pro": { - id: "google/gemini-3-pro", - name: "Gemini-3-Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-10-22", - last_updated: "2025-10-22", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 1.6, output: 9.6, cache_read: 0.16 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-2.5-pro": { - id: "google/gemini-2.5-pro", - name: "Gemini-2.5-Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-02-05", - last_updated: "2025-02-05", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.87, output: 7, cache_read: 0.087 }, - limit: { context: 1065535, output: 65535 }, - }, - "google/gemini-2.0-flash": { - id: "google/gemini-2.0-flash", - name: "Gemini-2.0-Flash", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.42 }, - limit: { context: 990000, output: 8192 }, - }, - "google/veo-3-fast": { - id: "google/veo-3-fast", - name: "Veo-3-Fast", - family: "veo", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-10-13", - last_updated: "2025-10-13", - modalities: { input: ["text"], output: ["video"] }, - open_weights: false, - limit: { context: 480, output: 0 }, - }, - "google/imagen-4-fast": { - id: "google/imagen-4-fast", - name: "Imagen-4-Fast", - family: "imagen", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-06-25", - last_updated: "2025-06-25", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 480, output: 0 }, - }, - "lumalabs/ray2": { - id: "lumalabs/ray2", - name: "Ray2", - family: "ray", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-02-20", - last_updated: "2025-02-20", - modalities: { input: ["text", "image"], output: ["video"] }, - open_weights: false, - limit: { context: 5000, output: 0 }, - }, - "poetools/claude-code": { - id: "poetools/claude-code", - name: "claude-code", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-11-27", - last_updated: "2025-11-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "openai/gpt-5.3-codex": { - id: "openai/gpt-5.3-codex", - name: "GPT-5.3-Codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2026-02-10", - last_updated: "2026-02-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.6, output: 13, cache_read: 0.16 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5-codex": { - id: "openai/gpt-5-codex", - name: "GPT-5-Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-09-23", - last_updated: "2025-09-23", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 9 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5-pro": { - id: "openai/gpt-5-pro", - name: "GPT-5-Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-10-06", - last_updated: "2025-10-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 14, output: 110 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-4o-mini": { - id: "openai/gpt-4o-mini", - name: "GPT-4o-mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.54, cache_read: 0.068 }, - limit: { context: 124096, output: 4096 }, - }, - "openai/gpt-5.1-codex-max": { - id: "openai/gpt-5.1-codex-max", - name: "GPT 5.1 Codex Max", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 9, cache_read: 0.11 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.2-codex": { - id: "openai/gpt-5.2-codex", - name: "GPT-5.2-Codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2026-01-14", - last_updated: "2026-01-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.6, output: 13, cache_read: 0.16 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/o3-deep-research": { - id: "openai/o3-deep-research", - name: "o3-deep-research", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-06-27", - last_updated: "2025-06-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 9, output: 36, cache_read: 2.2 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/o1": { - id: "openai/o1", - name: "o1", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2024-12-18", - last_updated: "2024-12-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 14, output: 54 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-5.1": { - id: "openai/gpt-5.1", - name: "GPT-5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-11-12", - last_updated: "2025-11-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 9, cache_read: 0.11 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/o4-mini-deep-research": { - id: "openai/o4-mini-deep-research", - name: "o4-mini-deep-research", - family: "o-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-06-27", - last_updated: "2025-06-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.8, output: 7.2, cache_read: 0.45 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-5-chat": { - id: "openai/gpt-5-chat", - name: "GPT-5-Chat", - family: "gpt-codex", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 9, cache_read: 0.11 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/o3": { - id: "openai/o3", - name: "o3", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.8, output: 7.2, cache_read: 0.45 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4-classic": { - id: "openai/gpt-4-classic", - name: "GPT-4-Classic", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-03-25", - last_updated: "2024-03-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 27, output: 54 }, - limit: { context: 8192, output: 4096 }, - }, - "openai/gpt-5.3-instant": { - id: "openai/gpt-5.3-instant", - name: "GPT-5.3-Instant", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2026-03-03", - last_updated: "2026-03-03", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.6, output: 13, cache_read: 0.16 }, - limit: { context: 128000, input: 111616, output: 16384 }, - }, - "openai/gpt-image-1.5": { - id: "openai/gpt-image-1.5", - name: "gpt-image-1.5", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-12-16", - last_updated: "2025-12-16", - modalities: { input: ["text", "image"], output: ["image"] }, - open_weights: false, - limit: { context: 128000, output: 0 }, - }, - "openai/gpt-4.1-nano": { - id: "openai/gpt-4.1-nano", - name: "GPT-4.1-nano", - family: "gpt-nano", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09, output: 0.36, cache_read: 0.022 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/gpt-image-1-mini": { - id: "openai/gpt-image-1-mini", - name: "GPT-Image-1-Mini", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-08-26", - last_updated: "2025-08-26", - modalities: { input: ["text", "image"], output: ["image"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "openai/sora-2-pro": { - id: "openai/sora-2-pro", - name: "Sora-2-Pro", - family: "sora", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-10-06", - last_updated: "2025-10-06", - modalities: { input: ["text", "image"], output: ["video"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "openai/gpt-3.5-turbo": { - id: "openai/gpt-3.5-turbo", - name: "GPT-3.5-Turbo", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2023-09-13", - last_updated: "2023-09-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.45, output: 1.4 }, - limit: { context: 16384, output: 2048 }, - }, - "openai/gpt-5.1-codex-mini": { - id: "openai/gpt-5.1-codex-mini", - name: "GPT-5.1-Codex-Mini", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-11-12", - last_updated: "2025-11-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.22, output: 1.8, cache_read: 0.022 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.2": { - id: "openai/gpt-5.2", - name: "GPT-5.2", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.6, output: 13, cache_read: 0.16 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-4.1": { - id: "openai/gpt-4.1", - name: "GPT-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.8, output: 7.2, cache_read: 0.45 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/gpt-4o-aug": { - id: "openai/gpt-4o-aug", - name: "GPT-4o-Aug", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-11-21", - last_updated: "2024-11-21", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.2, output: 9, cache_read: 1.1 }, - limit: { context: 128000, output: 8192 }, - }, - "openai/o3-pro": { - id: "openai/o3-pro", - name: "o3-pro", - family: "o-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-06-10", - last_updated: "2025-06-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 18, output: 72 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4-turbo": { - id: "openai/gpt-4-turbo", - name: "GPT-4-Turbo", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2023-09-13", - last_updated: "2023-09-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 9, output: 27 }, - limit: { context: 128000, output: 4096 }, - }, - "openai/gpt-image-1": { - id: "openai/gpt-image-1", - name: "GPT-Image-1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-03-31", - last_updated: "2025-03-31", - modalities: { input: ["text", "image"], output: ["image"] }, - open_weights: false, - limit: { context: 128000, output: 0 }, - }, - "openai/sora-2": { - id: "openai/sora-2", - name: "Sora-2", - family: "sora", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-10-06", - last_updated: "2025-10-06", - modalities: { input: ["text", "image"], output: ["video"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "openai/gpt-3.5-turbo-raw": { - id: "openai/gpt-3.5-turbo-raw", - name: "GPT-3.5-Turbo-Raw", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2023-09-27", - last_updated: "2023-09-27", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.45, output: 1.4 }, - limit: { context: 4524, output: 2048 }, - }, - "openai/gpt-4o-mini-search": { - id: "openai/gpt-4o-mini-search", - name: "GPT-4o-mini-Search", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-03-11", - last_updated: "2025-03-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.54 }, - limit: { context: 128000, output: 8192 }, - }, - "openai/gpt-5": { - id: "openai/gpt-5", - name: "GPT-5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 9, cache_read: 0.11 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/o4-mini": { - id: "openai/o4-mini", - name: "o4-mini", - family: "o-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.99, output: 4, cache_read: 0.25 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4.1-mini": { - id: "openai/gpt-4.1-mini", - name: "GPT-4.1-mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.36, output: 1.4, cache_read: 0.09 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/gpt-5.4": { - id: "openai/gpt-5.4", - name: "GPT-5.4", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2026-02-26", - last_updated: "2026-02-26", - modalities: { input: ["text", "image", "pdf"], output: ["image"] }, - open_weights: false, - cost: { input: 2.2, output: 14, cache_read: 0.22 }, - limit: { context: 1050000, input: 922000, output: 128000 }, - }, - "openai/gpt-5.4-pro": { - id: "openai/gpt-5.4-pro", - name: "GPT-5.4-Pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image"], output: ["image"] }, - open_weights: false, - cost: { input: 27, output: 160 }, - limit: { context: 1050000, input: 922000, output: 128000 }, - }, - "openai/o1-pro": { - id: "openai/o1-pro", - name: "o1-pro", - family: "o-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-03-19", - last_updated: "2025-03-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 140, output: 540 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-5.1-codex": { - id: "openai/gpt-5.1-codex", - name: "GPT-5.1-Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-11-12", - last_updated: "2025-11-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 9, cache_read: 0.11 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/chatgpt-4o-latest": { - id: "openai/chatgpt-4o-latest", - name: "ChatGPT-4o-Latest", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-08-14", - last_updated: "2024-08-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 4.5, output: 14 }, - limit: { context: 128000, output: 8192 }, - }, - "openai/gpt-5.2-pro": { - id: "openai/gpt-5.2-pro", - name: "GPT-5.2-Pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 19, output: 150 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/dall-e-3": { - id: "openai/dall-e-3", - name: "DALL-E-3", - family: "dall-e", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2023-11-06", - last_updated: "2023-11-06", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 800, output: 0 }, - }, - "openai/o3-mini": { - id: "openai/o3-mini", - name: "o3-mini", - family: "o-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-01-31", - last_updated: "2025-01-31", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.99, output: 4 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4o-search": { - id: "openai/gpt-4o-search", - name: "GPT-4o-Search", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-03-11", - last_updated: "2025-03-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.2, output: 9 }, - limit: { context: 128000, output: 8192 }, - }, - "openai/gpt-5-mini": { - id: "openai/gpt-5-mini", - name: "GPT-5-mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-06-25", - last_updated: "2025-06-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.22, output: 1.8, cache_read: 0.022 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-4-classic-0314": { - id: "openai/gpt-4-classic-0314", - name: "GPT-4-Classic-0314", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-08-26", - last_updated: "2024-08-26", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 27, output: 54 }, - limit: { context: 8192, output: 4096 }, - }, - "openai/gpt-5-nano": { - id: "openai/gpt-5-nano", - name: "GPT-5-nano", - family: "gpt-nano", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.045, output: 0.36, cache_read: 0.0045 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-3.5-turbo-instruct": { - id: "openai/gpt-3.5-turbo-instruct", - name: "GPT-3.5-Turbo-Instruct", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2023-09-20", - last_updated: "2023-09-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.4, output: 1.8 }, - limit: { context: 3500, output: 1024 }, - }, - "openai/gpt-5.2-instant": { - id: "openai/gpt-5.2-instant", - name: "GPT-5.2-Instant", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.6, output: 13, cache_read: 0.16 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/o3-mini-high": { - id: "openai/o3-mini-high", - name: "o3-mini-high", - family: "o-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-01-31", - last_updated: "2025-01-31", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.99, output: 4 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4o": { - id: "openai/gpt-4o", - name: "GPT-4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 8192 }, - }, - "openai/gpt-5.1-instant": { - id: "openai/gpt-5.1-instant", - name: "GPT-5.1-Instant", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-11-12", - last_updated: "2025-11-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 9, cache_read: 0.11 }, - limit: { context: 128000, output: 16384 }, - }, - "topazlabs-co/topazlabs": { - id: "topazlabs-co/topazlabs", - name: "TopazLabs", - family: "topazlabs", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 204, output: 0 }, - }, - "runwayml/runway": { - id: "runwayml/runway", - name: "Runway", - family: "runway", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-10-11", - last_updated: "2024-10-11", - modalities: { input: ["text", "image"], output: ["video"] }, - open_weights: false, - limit: { context: 256, output: 0 }, - }, - "runwayml/runway-gen-4-turbo": { - id: "runwayml/runway-gen-4-turbo", - name: "Runway-Gen-4-Turbo", - family: "runway", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-05-09", - last_updated: "2025-05-09", - modalities: { input: ["text", "image"], output: ["video"] }, - open_weights: false, - limit: { context: 256, output: 0 }, - }, - "anthropic/claude-sonnet-3.5-june": { - id: "anthropic/claude-sonnet-3.5-june", - name: "Claude-Sonnet-3.5-June", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-11-18", - last_updated: "2024-11-18", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.6, output: 13, cache_read: 0.26, cache_write: 3.2 }, - limit: { context: 189096, output: 8192 }, - }, - "anthropic/claude-opus-4.1": { - id: "anthropic/claude-opus-4.1", - name: "Claude-Opus-4.1", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 13, output: 64, cache_read: 1.3, cache_write: 16 }, - limit: { context: 196608, output: 32000 }, - }, - "anthropic/claude-sonnet-3.5": { - id: "anthropic/claude-sonnet-3.5", - name: "Claude-Sonnet-3.5", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-06-05", - last_updated: "2024-06-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.6, output: 13, cache_read: 0.26, cache_write: 3.2 }, - limit: { context: 189096, output: 8192 }, - }, - "anthropic/claude-haiku-3": { - id: "anthropic/claude-haiku-3", - name: "Claude-Haiku-3", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-03-09", - last_updated: "2024-03-09", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.21, output: 1.1, cache_read: 0.021, cache_write: 0.26 }, - limit: { context: 189096, output: 8192 }, - }, - "anthropic/claude-haiku-3.5": { - id: "anthropic/claude-haiku-3.5", - name: "Claude-Haiku-3.5", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-10-01", - last_updated: "2024-10-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.68, output: 3.4, cache_read: 0.068, cache_write: 0.85 }, - limit: { context: 189096, output: 8192 }, - }, - "anthropic/claude-sonnet-4.6": { - id: "anthropic/claude-sonnet-4.6", - name: "Claude-Sonnet-4.6", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.6, output: 13, cache_read: 0.26, cache_write: 3.2 }, - limit: { context: 983040, output: 128000 }, - }, - "anthropic/claude-haiku-4.5": { - id: "anthropic/claude-haiku-4.5", - name: "Claude-Haiku-4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.85, output: 4.3, cache_read: 0.085, cache_write: 1.1 }, - limit: { context: 192000, output: 64000 }, - }, - "anthropic/claude-opus-4.5": { - id: "anthropic/claude-opus-4.5", - name: "Claude-Opus-4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-11-21", - last_updated: "2025-11-21", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 4.3, output: 21, cache_read: 0.43, cache_write: 5.3 }, - limit: { context: 196608, output: 64000 }, - }, - "anthropic/claude-opus-4": { - id: "anthropic/claude-opus-4", - name: "Claude-Opus-4", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-05-21", - last_updated: "2025-05-21", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 13, output: 64, cache_read: 1.3, cache_write: 16 }, - limit: { context: 192512, output: 28672 }, - }, - "anthropic/claude-sonnet-4": { - id: "anthropic/claude-sonnet-4", - name: "Claude-Sonnet-4", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-05-21", - last_updated: "2025-05-21", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.6, output: 13, cache_read: 0.26, cache_write: 3.2 }, - limit: { context: 983040, output: 64000 }, - }, - "anthropic/claude-sonnet-4.5": { - id: "anthropic/claude-sonnet-4.5", - name: "Claude-Sonnet-4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-09-26", - last_updated: "2025-09-26", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.6, output: 13, cache_read: 0.26, cache_write: 3.2 }, - limit: { context: 983040, output: 32768 }, - }, - "anthropic/claude-opus-4.6": { - id: "anthropic/claude-opus-4.6", - name: "Claude-Opus-4.6", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2026-02-04", - last_updated: "2026-02-04", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 4.3, output: 21, cache_read: 0.43, cache_write: 5.3 }, - limit: { context: 983040, output: 128000 }, - }, - "anthropic/claude-sonnet-3.7": { - id: "anthropic/claude-sonnet-3.7", - name: "Claude-Sonnet-3.7", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.6, output: 13, cache_read: 0.26, cache_write: 3.2 }, - limit: { context: 196608, output: 128000 }, - }, - "trytako/tako": { - id: "trytako/tako", - name: "Tako", - family: "tako", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-08-15", - last_updated: "2024-08-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 2048, output: 0 }, - }, - "elevenlabs/elevenlabs-music": { - id: "elevenlabs/elevenlabs-music", - name: "ElevenLabs-Music", - family: "elevenlabs", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-08-29", - last_updated: "2025-08-29", - modalities: { input: ["text"], output: ["audio"] }, - open_weights: false, - limit: { context: 2000, output: 0 }, - }, - "elevenlabs/elevenlabs-v3": { - id: "elevenlabs/elevenlabs-v3", - name: "ElevenLabs-v3", - family: "elevenlabs", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-06-05", - last_updated: "2025-06-05", - modalities: { input: ["text"], output: ["audio"] }, - open_weights: false, - limit: { context: 128000, output: 0 }, - }, - "elevenlabs/elevenlabs-v2.5-turbo": { - id: "elevenlabs/elevenlabs-v2.5-turbo", - name: "ElevenLabs-v2.5-Turbo", - family: "elevenlabs", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-10-28", - last_updated: "2024-10-28", - modalities: { input: ["text"], output: ["audio"] }, - open_weights: false, - limit: { context: 128000, output: 0 }, - }, - "cerebras/llama-3.1-8b-cs": { - id: "cerebras/llama-3.1-8b-cs", - name: "llama-3.1-8b-cs", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-05-13", - last_updated: "2025-05-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "cerebras/gpt-oss-120b-cs": { - id: "cerebras/gpt-oss-120b-cs", - name: "gpt-oss-120b-cs", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-08-06", - last_updated: "2025-08-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "cerebras/qwen3-235b-2507-cs": { - id: "cerebras/qwen3-235b-2507-cs", - name: "qwen3-235b-2507-cs", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-08-06", - last_updated: "2025-08-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "cerebras/llama-3.3-70b-cs": { - id: "cerebras/llama-3.3-70b-cs", - name: "llama-3.3-70b-cs", - attachment: true, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-05-13", - last_updated: "2025-05-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "cerebras/qwen3-32b-cs": { - id: "cerebras/qwen3-32b-cs", - name: "qwen3-32b-cs", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-05-15", - last_updated: "2025-05-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "xai/grok-4-fast-reasoning": { - id: "xai/grok-4-fast-reasoning", - name: "Grok-4-Fast-Reasoning", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-09-16", - last_updated: "2025-09-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 128000 }, - }, - "xai/grok-3": { - id: "xai/grok-3", - name: "Grok 3", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-04-11", - last_updated: "2025-04-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75 }, - limit: { context: 131072, output: 8192 }, - }, - "xai/grok-code-fast-1": { - id: "xai/grok-code-fast-1", - name: "Grok Code Fast 1", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-08-22", - last_updated: "2025-08-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.5, cache_read: 0.02 }, - limit: { context: 256000, output: 128000 }, - }, - "xai/grok-4.1-fast-reasoning": { - id: "xai/grok-4.1-fast-reasoning", - name: "Grok-4.1-Fast-Reasoning", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 2000000, output: 30000 }, - }, - "xai/grok-4": { - id: "xai/grok-4", - name: "Grok-4", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-07-10", - last_updated: "2025-07-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75 }, - limit: { context: 256000, output: 128000 }, - }, - "xai/grok-4.1-fast-non-reasoning": { - id: "xai/grok-4.1-fast-non-reasoning", - name: "Grok-4.1-Fast-Non-Reasoning", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - limit: { context: 2000000, output: 30000 }, - }, - "xai/grok-3-mini": { - id: "xai/grok-3-mini", - name: "Grok 3 Mini", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-04-11", - last_updated: "2025-04-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.5, cache_read: 0.075 }, - limit: { context: 131072, output: 8192 }, - }, - "xai/grok-4-fast-non-reasoning": { - id: "xai/grok-4-fast-non-reasoning", - name: "Grok-4-Fast-Non-Reasoning", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-09-16", - last_updated: "2025-09-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 128000 }, - }, - }, - }, - "amazon-bedrock": { - id: "amazon-bedrock", - env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION", "AWS_BEARER_TOKEN_BEDROCK"], - npm: "@ai-sdk/amazon-bedrock", - name: "Amazon Bedrock", - doc: "https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html", - models: { - "deepseek.r1-v1:0": { - id: "deepseek.r1-v1:0", - name: "DeepSeek-R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-01-20", - last_updated: "2025-05-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.35, output: 5.4 }, - limit: { context: 128000, output: 32768 }, - }, - "meta.llama3-1-70b-instruct-v1:0": { - id: "meta.llama3-1-70b-instruct-v1:0", - name: "Llama 3.1 70B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.72, output: 0.72 }, - limit: { context: 128000, output: 4096 }, - }, - "qwen.qwen3-coder-480b-a35b-v1:0": { - id: "qwen.qwen3-coder-480b-a35b-v1:0", - name: "Qwen3 Coder 480B A35B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-09-18", - last_updated: "2025-09-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.22, output: 1.8 }, - limit: { context: 131072, output: 65536 }, - }, - "eu.anthropic.claude-sonnet-4-6": { - id: "eu.anthropic.claude-sonnet-4-6", - name: "Claude Sonnet 4.6 (EU)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 200000, output: 64000 }, - }, - "eu.anthropic.claude-haiku-4-5-20251001-v1:0": { - id: "eu.anthropic.claude-haiku-4-5-20251001-v1:0", - name: "Claude Haiku 4.5 (EU)", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "mistral.mistral-large-3-675b-instruct": { - id: "mistral.mistral-large-3-675b-instruct", - name: "Mistral Large 3", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-12-02", - last_updated: "2025-12-02", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 256000, output: 8192 }, - }, - "openai.gpt-oss-120b-1:0": { - id: "openai.gpt-oss-120b-1:0", - name: "gpt-oss-120b", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 4096 }, - }, - "us.anthropic.claude-opus-4-20250514-v1:0": { - id: "us.anthropic.claude-opus-4-20250514-v1:0", - name: "Claude Opus 4 (US)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "nvidia.nemotron-nano-12b-v2": { - id: "nvidia.nemotron-nano-12b-v2", - name: "NVIDIA Nemotron Nano 12B v2 VL BF16", - family: "nemotron", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 128000, output: 4096 }, - }, - "anthropic.claude-3-7-sonnet-20250219-v1:0": { - id: "anthropic.claude-3-7-sonnet-20250219-v1:0", - name: "Claude Sonnet 3.7", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 8192 }, - }, - "anthropic.claude-sonnet-4-6": { - id: "anthropic.claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 200000, output: 64000 }, - }, - "minimax.minimax-m2.1": { - id: "minimax.minimax-m2.1", - name: "MiniMax M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 204800, output: 131072 }, - }, - "global.anthropic.claude-opus-4-5-20251101-v1:0": { - id: "global.anthropic.claude-opus-4-5-20251101-v1:0", - name: "Claude Opus 4.5 (Global)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-08-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "mistral.ministral-3-8b-instruct": { - id: "mistral.ministral-3-8b-instruct", - name: "Ministral 3 8B", - family: "ministral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 128000, output: 4096 }, - }, - "openai.gpt-oss-safeguard-20b": { - id: "openai.gpt-oss-safeguard-20b", - name: "GPT OSS Safeguard 20B", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.2 }, - limit: { context: 128000, output: 4096 }, - }, - "amazon.nova-lite-v1:0": { - id: "amazon.nova-lite-v1:0", - name: "Nova Lite", - family: "nova-lite", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.06, output: 0.24, cache_read: 0.015 }, - limit: { context: 300000, output: 8192 }, - }, - "eu.anthropic.claude-sonnet-4-5-20250929-v1:0": { - id: "eu.anthropic.claude-sonnet-4-5-20250929-v1:0", - name: "Claude Sonnet 4.5 (EU)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "mistral.pixtral-large-2502-v1:0": { - id: "mistral.pixtral-large-2502-v1:0", - name: "Pixtral Large (25.02)", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-04-08", - last_updated: "2025-04-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6 }, - limit: { context: 128000, output: 8192 }, - }, - "google.gemma-3-12b-it": { - id: "google.gemma-3-12b-it", - name: "Google Gemma 3 12B", - family: "gemma", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-12", - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.049999999999999996, output: 0.09999999999999999 }, - limit: { context: 131072, output: 8192 }, - }, - "meta.llama3-1-8b-instruct-v1:0": { - id: "meta.llama3-1-8b-instruct-v1:0", - name: "Llama 3.1 8B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.22, output: 0.22 }, - limit: { context: 128000, output: 4096 }, - }, - "mistral.devstral-2-123b": { - id: "mistral.devstral-2-123b", - name: "Devstral 2 123B", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2 }, - limit: { context: 256000, output: 8192 }, - }, - "anthropic.claude-sonnet-4-5-20250929-v1:0": { - id: "anthropic.claude-sonnet-4-5-20250929-v1:0", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "meta.llama4-maverick-17b-instruct-v1:0": { - id: "meta.llama4-maverick-17b-instruct-v1:0", - name: "Llama 4 Maverick 17B Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.24, output: 0.97 }, - limit: { context: 1000000, output: 16384 }, - }, - "mistral.ministral-3-14b-instruct": { - id: "mistral.ministral-3-14b-instruct", - name: "Ministral 14B 3.0", - family: "ministral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 128000, output: 4096 }, - }, - "minimax.minimax-m2": { - id: "minimax.minimax-m2", - name: "MiniMax M2", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-10-27", - last_updated: "2025-10-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 204608, output: 128000 }, - }, - "amazon.nova-micro-v1:0": { - id: "amazon.nova-micro-v1:0", - name: "Nova Micro", - family: "nova-micro", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.035, output: 0.14, cache_read: 0.00875 }, - limit: { context: 128000, output: 8192 }, - }, - "anthropic.claude-3-5-sonnet-20241022-v2:0": { - id: "anthropic.claude-3-5-sonnet-20241022-v2:0", - name: "Claude Sonnet 3.5 v2", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 8192 }, - }, - "nvidia.nemotron-nano-3-30b": { - id: "nvidia.nemotron-nano-3-30b", - name: "NVIDIA Nemotron Nano 3 30B", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.24 }, - limit: { context: 128000, output: 4096 }, - }, - "anthropic.claude-sonnet-4-20250514-v1:0": { - id: "anthropic.claude-sonnet-4-20250514-v1:0", - name: "Claude Sonnet 4", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "qwen.qwen3-vl-235b-a22b": { - id: "qwen.qwen3-vl-235b-a22b", - name: "Qwen/Qwen3-VL-235B-A22B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-04", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.5 }, - limit: { context: 262000, output: 262000 }, - }, - "global.anthropic.claude-opus-4-6-v1": { - id: "global.anthropic.claude-opus-4-6-v1", - name: "Claude Opus 4.6 (Global)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 5, - output: 25, - cache_read: 0.5, - cache_write: 6.25, - context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 }, - }, - limit: { context: 200000, output: 128000 }, - }, - "writer.palmyra-x4-v1:0": { - id: "writer.palmyra-x4-v1:0", - name: "Palmyra X4", - family: "palmyra", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-04-28", - last_updated: "2025-04-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 122880, output: 8192 }, - }, - "amazon.nova-pro-v1:0": { - id: "amazon.nova-pro-v1:0", - name: "Nova Pro", - family: "nova-pro", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 3.2, cache_read: 0.2 }, - limit: { context: 300000, output: 8192 }, - }, - "us.anthropic.claude-opus-4-5-20251101-v1:0": { - id: "us.anthropic.claude-opus-4-5-20251101-v1:0", - name: "Claude Opus 4.5 (US)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-08-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "meta.llama3-2-90b-instruct-v1:0": { - id: "meta.llama3-2-90b-instruct-v1:0", - name: "Llama 3.2 90B Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.72, output: 0.72 }, - limit: { context: 128000, output: 4096 }, - }, - "us.anthropic.claude-opus-4-6-v1": { - id: "us.anthropic.claude-opus-4-6-v1", - name: "Claude Opus 4.6 (US)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 5, - output: 25, - cache_read: 0.5, - cache_write: 6.25, - context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 }, - }, - limit: { context: 200000, output: 128000 }, - }, - "google.gemma-3-4b-it": { - id: "google.gemma-3-4b-it", - name: "Gemma 3 4B IT", - family: "gemma", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.04, output: 0.08 }, - limit: { context: 128000, output: 4096 }, - }, - "anthropic.claude-opus-4-6-v1": { - id: "anthropic.claude-opus-4-6-v1", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 5, - output: 25, - cache_read: 0.5, - cache_write: 6.25, - context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 }, - }, - limit: { context: 200000, output: 128000 }, - }, - "zai.glm-4.7-flash": { - id: "zai.glm-4.7-flash", - name: "GLM-4.7-Flash", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.4 }, - limit: { context: 200000, output: 131072 }, - }, - "anthropic.claude-opus-4-20250514-v1:0": { - id: "anthropic.claude-opus-4-20250514-v1:0", - name: "Claude Opus 4", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "global.anthropic.claude-sonnet-4-6": { - id: "global.anthropic.claude-sonnet-4-6", - name: "Claude Sonnet 4.6 (Global)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 200000, output: 64000 }, - }, - "meta.llama3-2-1b-instruct-v1:0": { - id: "meta.llama3-2-1b-instruct-v1:0", - name: "Llama 3.2 1B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 131000, output: 4096 }, - }, - "anthropic.claude-opus-4-1-20250805-v1:0": { - id: "anthropic.claude-opus-4-1-20250805-v1:0", - name: "Claude Opus 4.1", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "meta.llama4-scout-17b-instruct-v1:0": { - id: "meta.llama4-scout-17b-instruct-v1:0", - name: "Llama 4 Scout 17B Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.17, output: 0.66 }, - limit: { context: 3500000, output: 16384 }, - }, - "deepseek.v3.2": { - id: "deepseek.v3.2", - name: "DeepSeek-V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2026-02-06", - last_updated: "2026-02-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.62, output: 1.85 }, - limit: { context: 163840, output: 81920 }, - }, - "deepseek.v3-v1:0": { - id: "deepseek.v3-v1:0", - name: "DeepSeek-V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-09-18", - last_updated: "2025-09-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.58, output: 1.68 }, - limit: { context: 163840, output: 81920 }, - }, - "mistral.ministral-3-3b-instruct": { - id: "mistral.ministral-3-3b-instruct", - name: "Ministral 3 3B", - family: "ministral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-12-02", - last_updated: "2025-12-02", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 256000, output: 8192 }, - }, - "global.anthropic.claude-haiku-4-5-20251001-v1:0": { - id: "global.anthropic.claude-haiku-4-5-20251001-v1:0", - name: "Claude Haiku 4.5 (Global)", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "nvidia.nemotron-nano-9b-v2": { - id: "nvidia.nemotron-nano-9b-v2", - name: "NVIDIA Nemotron Nano 9B v2", - family: "nemotron", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.06, output: 0.23 }, - limit: { context: 128000, output: 4096 }, - }, - "writer.palmyra-x5-v1:0": { - id: "writer.palmyra-x5-v1:0", - name: "Palmyra X5", - family: "palmyra", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-04-28", - last_updated: "2025-04-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 6 }, - limit: { context: 1040000, output: 8192 }, - }, - "meta.llama3-3-70b-instruct-v1:0": { - id: "meta.llama3-3-70b-instruct-v1:0", - name: "Llama 3.3 70B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.72, output: 0.72 }, - limit: { context: 128000, output: 4096 }, - }, - "zai.glm-4.7": { - id: "zai.glm-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 204800, output: 131072 }, - }, - "moonshot.kimi-k2-thinking": { - id: "moonshot.kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - release_date: "2025-12-02", - last_updated: "2025-12-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5 }, - limit: { context: 256000, output: 256000 }, - }, - "anthropic.claude-3-haiku-20240307-v1:0": { - id: "anthropic.claude-3-haiku-20240307-v1:0", - name: "Claude Haiku 3", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-02", - release_date: "2024-03-13", - last_updated: "2024-03-13", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1.25 }, - limit: { context: 200000, output: 4096 }, - }, - "us.anthropic.claude-sonnet-4-5-20250929-v1:0": { - id: "us.anthropic.claude-sonnet-4-5-20250929-v1:0", - name: "Claude Sonnet 4.5 (US)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "openai.gpt-oss-20b-1:0": { - id: "openai.gpt-oss-20b-1:0", - name: "gpt-oss-20b", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.3 }, - limit: { context: 128000, output: 4096 }, - }, - "us.anthropic.claude-sonnet-4-6": { - id: "us.anthropic.claude-sonnet-4-6", - name: "Claude Sonnet 4.6 (US)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 200000, output: 64000 }, - }, - "meta.llama3-2-11b-instruct-v1:0": { - id: "meta.llama3-2-11b-instruct-v1:0", - name: "Llama 3.2 11B Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.16, output: 0.16 }, - limit: { context: 128000, output: 4096 }, - }, - "eu.anthropic.claude-opus-4-5-20251101-v1:0": { - id: "eu.anthropic.claude-opus-4-5-20251101-v1:0", - name: "Claude Opus 4.5 (EU)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-08-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "meta.llama3-1-405b-instruct-v1:0": { - id: "meta.llama3-1-405b-instruct-v1:0", - name: "Llama 3.1 405B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.4, output: 2.4 }, - limit: { context: 128000, output: 4096 }, - }, - "qwen.qwen3-next-80b-a3b": { - id: "qwen.qwen3-next-80b-a3b", - name: "Qwen/Qwen3-Next-80B-A3B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 1.4 }, - limit: { context: 262000, output: 262000 }, - }, - "us.anthropic.claude-sonnet-4-20250514-v1:0": { - id: "us.anthropic.claude-sonnet-4-20250514-v1:0", - name: "Claude Sonnet 4 (US)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "qwen.qwen3-coder-30b-a3b-v1:0": { - id: "qwen.qwen3-coder-30b-a3b-v1:0", - name: "Qwen3 Coder 30B A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-09-18", - last_updated: "2025-09-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 262144, output: 131072 }, - }, - "us.anthropic.claude-haiku-4-5-20251001-v1:0": { - id: "us.anthropic.claude-haiku-4-5-20251001-v1:0", - name: "Claude Haiku 4.5 (US)", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "qwen.qwen3-235b-a22b-2507-v1:0": { - id: "qwen.qwen3-235b-a22b-2507-v1:0", - name: "Qwen3 235B A22B 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-09-18", - last_updated: "2025-09-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.22, output: 0.88 }, - limit: { context: 262144, output: 131072 }, - }, - "openai.gpt-oss-safeguard-120b": { - id: "openai.gpt-oss-safeguard-120b", - name: "GPT OSS Safeguard 120B", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 4096 }, - }, - "anthropic.claude-3-5-sonnet-20240620-v1:0": { - id: "anthropic.claude-3-5-sonnet-20240620-v1:0", - name: "Claude Sonnet 3.5", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-06-20", - last_updated: "2024-06-20", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 8192 }, - }, - "mistral.voxtral-small-24b-2507": { - id: "mistral.voxtral-small-24b-2507", - name: "Voxtral Small 24B 2507", - family: "mistral", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-01", - last_updated: "2025-07-01", - modalities: { input: ["text", "audio"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.35 }, - limit: { context: 32000, output: 8192 }, - }, - "anthropic.claude-haiku-4-5-20251001-v1:0": { - id: "anthropic.claude-haiku-4-5-20251001-v1:0", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "meta.llama3-2-3b-instruct-v1:0": { - id: "meta.llama3-2-3b-instruct-v1:0", - name: "Llama 3.2 3B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 131000, output: 4096 }, - }, - "google.gemma-3-27b-it": { - id: "google.gemma-3-27b-it", - name: "Google Gemma 3 27B Instruct", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-27", - last_updated: "2025-07-27", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.12, output: 0.2 }, - limit: { context: 202752, output: 8192 }, - }, - "us.anthropic.claude-opus-4-1-20250805-v1:0": { - id: "us.anthropic.claude-opus-4-1-20250805-v1:0", - name: "Claude Opus 4.1 (US)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "global.anthropic.claude-sonnet-4-20250514-v1:0": { - id: "global.anthropic.claude-sonnet-4-20250514-v1:0", - name: "Claude Sonnet 4 (Global)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic.claude-3-5-haiku-20241022-v1:0": { - id: "anthropic.claude-3-5-haiku-20241022-v1:0", - name: "Claude Haiku 3.5", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 }, - limit: { context: 200000, output: 8192 }, - }, - "eu.anthropic.claude-sonnet-4-20250514-v1:0": { - id: "eu.anthropic.claude-sonnet-4-20250514-v1:0", - name: "Claude Sonnet 4 (EU)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic.claude-opus-4-5-20251101-v1:0": { - id: "anthropic.claude-opus-4-5-20251101-v1:0", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-08-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "eu.anthropic.claude-opus-4-6-v1": { - id: "eu.anthropic.claude-opus-4-6-v1", - name: "Claude Opus 4.6 (EU)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 5, - output: 25, - cache_read: 0.5, - cache_write: 6.25, - context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 }, - }, - limit: { context: 200000, output: 128000 }, - }, - "amazon.nova-premier-v1:0": { - id: "amazon.nova-premier-v1:0", - name: "Nova Premier", - family: "nova", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 12.5 }, - limit: { context: 1000000, output: 16384 }, - }, - "amazon.nova-2-lite-v1:0": { - id: "amazon.nova-2-lite-v1:0", - name: "Nova 2 Lite", - family: "nova", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.33, output: 2.75 }, - limit: { context: 128000, output: 4096 }, - }, - "qwen.qwen3-32b-v1:0": { - id: "qwen.qwen3-32b-v1:0", - name: "Qwen3 32B (dense)", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-09-18", - last_updated: "2025-09-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 16384, output: 16384 }, - }, - "mistral.magistral-small-2509": { - id: "mistral.magistral-small-2509", - name: "Magistral Small 1.2", - family: "magistral", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-02", - last_updated: "2025-12-02", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 128000, output: 40000 }, - }, - "moonshotai.kimi-k2.5": { - id: "moonshotai.kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - release_date: "2026-02-06", - last_updated: "2026-02-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3 }, - limit: { context: 256000, output: 256000 }, - }, - "mistral.voxtral-mini-3b-2507": { - id: "mistral.voxtral-mini-3b-2507", - name: "Voxtral Mini 3B 2507", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["audio", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.04, output: 0.04 }, - limit: { context: 128000, output: 4096 }, - }, - "global.anthropic.claude-sonnet-4-5-20250929-v1:0": { - id: "global.anthropic.claude-sonnet-4-5-20250929-v1:0", - name: "Claude Sonnet 4.5 (Global)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - }, - }, - "alibaba-coding-plan-cn": { - id: "alibaba-coding-plan-cn", - env: ["ALIBABA_CODING_PLAN_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://coding.dashscope.aliyuncs.com/v1", - name: "Alibaba Coding Plan (China)", - doc: "https://help.aliyun.com/zh/model-studio/coding-plan", - models: { - "glm-5": { - id: "glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 202752, output: 16384 }, - }, - "MiniMax-M2.5": { - id: "MiniMax-M2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 196608, output: 24576 }, - }, - "qwen3-coder-next": { - id: "qwen3-coder-next", - name: "Qwen3 Coder Next", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-03", - last_updated: "2026-02-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 262144, output: 65536 }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 262144, output: 32768 }, - }, - "qwen3-max-2026-01-23": { - id: "qwen3-max-2026-01-23", - name: "Qwen3 Max", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-01-23", - last_updated: "2026-01-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 262144, output: 32768 }, - }, - "glm-4.7": { - id: "glm-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 202752, output: 16384 }, - }, - "qwen3.5-plus": { - id: "qwen3.5-plus", - name: "Qwen3.5 Plus", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-16", - last_updated: "2026-02-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 1000000, output: 65536 }, - }, - "qwen3-coder-plus": { - id: "qwen3-coder-plus", - name: "Qwen3 Coder Plus", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 1000000, output: 65536 }, - }, - }, - }, - "minimax-cn": { - id: "minimax-cn", - env: ["MINIMAX_API_KEY"], - npm: "@ai-sdk/anthropic", - api: "https://api.minimaxi.com/anthropic/v1", - name: "MiniMax (minimaxi.com)", - doc: "https://platform.minimaxi.com/docs/guides/quickstart", - models: { - "MiniMax-M2.5": { - id: "MiniMax-M2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.03, cache_write: 0.375 }, - limit: { context: 204800, output: 131072 }, - }, - "MiniMax-M2": { - id: "MiniMax-M2", - name: "MiniMax-M2", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-10-27", - last_updated: "2025-10-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 196608, output: 128000 }, - }, - "MiniMax-M2.5-highspeed": { - id: "MiniMax-M2.5-highspeed", - name: "MiniMax-M2.5-highspeed", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-13", - last_updated: "2026-02-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.4, cache_read: 0.06, cache_write: 0.375 }, - limit: { context: 204800, output: 131072 }, - }, - "MiniMax-M2.1": { - id: "MiniMax-M2.1", - name: "MiniMax-M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 204800, output: 131072 }, - }, - }, - }, - bailing: { - id: "bailing", - env: ["BAILING_API_TOKEN"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.tbox.cn/api/llm/v1/chat/completions", - name: "Bailing", - doc: "https://alipaytbox.yuque.com/sxs0ba/ling/intro", - models: { - "Ring-1T": { - id: "Ring-1T", - name: "Ring-1T", - family: "ring", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-06", - release_date: "2025-10", - last_updated: "2025-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.57, output: 2.29 }, - limit: { context: 128000, output: 32000 }, - }, - "Ling-1T": { - id: "Ling-1T", - name: "Ling-1T", - family: "ling", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2025-10", - last_updated: "2025-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.57, output: 2.29 }, - limit: { context: 128000, output: 32000 }, - }, - }, - }, - "azure-cognitive-services": { - id: "azure-cognitive-services", - env: ["AZURE_COGNITIVE_SERVICES_RESOURCE_NAME", "AZURE_COGNITIVE_SERVICES_API_KEY"], - npm: "@ai-sdk/azure", - name: "Azure Cognitive Services", - doc: "https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models", - models: { - "claude-opus-4-1": { - id: "claude-opus-4-1", - name: "Claude Opus 4.1", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - provider: { - npm: "@ai-sdk/anthropic", - api: "https://${AZURE_COGNITIVE_SERVICES_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1", - }, - }, - "claude-opus-4-6": { - id: "claude-opus-4-6", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 5, - output: 25, - cache_read: 0.5, - cache_write: 6.25, - context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 }, - }, - limit: { context: 200000, output: 128000 }, - provider: { - npm: "@ai-sdk/anthropic", - api: "https://${AZURE_COGNITIVE_SERVICES_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1", - }, - }, - "claude-haiku-4-5": { - id: "claude-haiku-4-5", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-02-31", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - provider: { - npm: "@ai-sdk/anthropic", - api: "https://${AZURE_COGNITIVE_SERVICES_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1", - }, - }, - "claude-opus-4-5": { - id: "claude-opus-4-5", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-08-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - provider: { - npm: "@ai-sdk/anthropic", - api: "https://${AZURE_COGNITIVE_SERVICES_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1", - }, - }, - "claude-sonnet-4-5": { - id: "claude-sonnet-4-5", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - provider: { - npm: "@ai-sdk/anthropic", - api: "https://${AZURE_COGNITIVE_SERVICES_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1", - }, - }, - "phi-3-small-8k-instruct": { - id: "phi-3-small-8k-instruct", - name: "Phi-3-small-instruct (8k)", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 8192, output: 2048 }, - }, - "gpt-4o": { - id: "gpt-4o", - name: "GPT-4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10, cache_read: 1.25 }, - limit: { context: 128000, output: 16384 }, - }, - "codestral-2501": { - id: "codestral-2501", - name: "Codestral 25.01", - family: "codestral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 256000, output: 256000 }, - }, - "mistral-small-2503": { - id: "mistral-small-2503", - name: "Mistral Small 3.1", - family: "mistral-small", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2025-03-01", - last_updated: "2025-03-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 128000, output: 32768 }, - }, - "o1-mini": { - id: "o1-mini", - name: "o1-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2023-09", - release_date: "2024-09-12", - last_updated: "2024-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.55 }, - limit: { context: 128000, output: 65536 }, - }, - "gpt-3.5-turbo-instruct": { - id: "gpt-3.5-turbo-instruct", - name: "GPT-3.5 Turbo Instruct", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2021-08", - release_date: "2023-09-21", - last_updated: "2023-09-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 2 }, - limit: { context: 4096, output: 4096 }, - }, - "gpt-5-nano": { - id: "gpt-5-nano", - name: "GPT-5 Nano", - family: "gpt-nano", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4, cache_read: 0.01 }, - limit: { context: 272000, output: 128000 }, - }, - "gpt-4": { - id: "gpt-4", - name: "GPT-4", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-11", - release_date: "2023-03-14", - last_updated: "2023-03-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 60, output: 120 }, - limit: { context: 8192, output: 8192 }, - }, - "gpt-3.5-turbo-1106": { - id: "gpt-3.5-turbo-1106", - name: "GPT-3.5 Turbo 1106", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2021-08", - release_date: "2023-11-06", - last_updated: "2023-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 2 }, - limit: { context: 16384, output: 16384 }, - }, - "phi-4-reasoning": { - id: "phi-4-reasoning", - name: "Phi-4-reasoning", - family: "phi", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.125, output: 0.5 }, - limit: { context: 32000, output: 4096 }, - }, - "phi-3-mini-128k-instruct": { - id: "phi-3-mini-128k-instruct", - name: "Phi-3-mini-instruct (128k)", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.52 }, - limit: { context: 128000, output: 4096 }, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - name: "GPT-5 Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.03 }, - limit: { context: 272000, output: 128000 }, - }, - "llama-4-maverick-17b-128e-instruct-fp8": { - id: "llama-4-maverick-17b-128e-instruct-fp8", - name: "Llama 4 Maverick 17B 128E Instruct FP8", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 1 }, - limit: { context: 128000, output: 8192 }, - }, - "grok-4-fast-non-reasoning": { - id: "grok-4-fast-non-reasoning", - name: "Grok 4 Fast (Non-Reasoning)", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-09-19", - last_updated: "2025-09-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "o3-mini": { - id: "o3-mini", - name: "o3-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05", - release_date: "2024-12-20", - last_updated: "2025-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.55 }, - limit: { context: 200000, output: 100000 }, - }, - "cohere-embed-v3-english": { - id: "cohere-embed-v3-english", - name: "Embed v3 English", - family: "cohere-embed", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2023-11-07", - last_updated: "2023-11-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0 }, - limit: { context: 512, output: 1024 }, - }, - "phi-3-medium-4k-instruct": { - id: "phi-3-medium-4k-instruct", - name: "Phi-3-medium-instruct (4k)", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.17, output: 0.68 }, - limit: { context: 4096, output: 1024 }, - }, - "cohere-embed-v3-multilingual": { - id: "cohere-embed-v3-multilingual", - name: "Embed v3 Multilingual", - family: "cohere-embed", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2023-11-07", - last_updated: "2023-11-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0 }, - limit: { context: 512, output: 1024 }, - }, - "gpt-3.5-turbo-0125": { - id: "gpt-3.5-turbo-0125", - name: "GPT-3.5 Turbo 0125", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2021-08", - release_date: "2024-01-25", - last_updated: "2024-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 16384, output: 16384 }, - }, - "phi-4-mini-reasoning": { - id: "phi-4-mini-reasoning", - name: "Phi-4-mini-reasoning", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 128000, output: 4096 }, - }, - "mistral-large-2411": { - id: "mistral-large-2411", - name: "Mistral Large 24.11", - family: "mistral-large", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2024-11-01", - last_updated: "2024-11-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6 }, - limit: { context: 128000, output: 32768 }, - }, - "gpt-5.1-codex": { - id: "gpt-5.1-codex", - name: "GPT-5.1 Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text", "image", "audio"], output: ["text", "image", "audio"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "meta-llama-3.1-8b-instruct": { - id: "meta-llama-3.1-8b-instruct", - name: "Meta-Llama-3.1-8B-Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.61 }, - limit: { context: 128000, output: 32768 }, - }, - "gpt-5.4-pro": { - id: "gpt-5.4-pro", - name: "GPT-5.4 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 30, output: 180 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "o1-preview": { - id: "o1-preview", - name: "o1-preview", - family: "o", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2023-09", - release_date: "2024-09-12", - last_updated: "2024-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 16.5, output: 66, cache_read: 8.25 }, - limit: { context: 128000, output: 32768 }, - }, - "meta-llama-3.1-70b-instruct": { - id: "meta-llama-3.1-70b-instruct", - name: "Meta-Llama-3.1-70B-Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.68, output: 3.54 }, - limit: { context: 128000, output: 32768 }, - }, - "phi-3-mini-4k-instruct": { - id: "phi-3-mini-4k-instruct", - name: "Phi-3-mini-instruct (4k)", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.52 }, - limit: { context: 4096, output: 1024 }, - }, - "codex-mini": { - id: "codex-mini", - name: "Codex Mini", - family: "gpt-codex-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-04", - release_date: "2025-05-16", - last_updated: "2025-05-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 6, cache_read: 0.375 }, - limit: { context: 200000, output: 100000 }, - }, - "gpt-5.4": { - id: "gpt-5.4", - name: "GPT-5.4", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 15, cache_read: 0.25 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-11-06", - last_updated: "2025-12-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5, cache_read: 0.15 }, - limit: { context: 262144, output: 262144 }, - }, - "phi-4-reasoning-plus": { - id: "phi-4-reasoning-plus", - name: "Phi-4-reasoning-plus", - family: "phi", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.125, output: 0.5 }, - limit: { context: 32000, output: 4096 }, - }, - "gpt-4.1-mini": { - id: "gpt-4.1-mini", - name: "GPT-4.1 mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-05", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.6, cache_read: 0.1 }, - limit: { context: 1047576, output: 32768 }, - }, - "phi-4": { - id: "phi-4", - name: "Phi-4", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.125, output: 0.5 }, - limit: { context: 128000, output: 4096 }, - }, - "o4-mini": { - id: "o4-mini", - name: "o4-mini", - family: "o-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.28 }, - limit: { context: 200000, output: 100000 }, - }, - "gpt-5": { - id: "gpt-5", - name: "GPT-5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 272000, output: 128000 }, - }, - "gpt-4-32k": { - id: "gpt-4-32k", - name: "GPT-4 32K", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-11", - release_date: "2023-03-14", - last_updated: "2023-03-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 60, output: 120 }, - limit: { context: 32768, output: 32768 }, - }, - "grok-3-mini": { - id: "grok-3-mini", - name: "Grok 3 Mini", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.5, reasoning: 0.5, cache_read: 0.075 }, - limit: { context: 131072, output: 8192 }, - }, - "cohere-embed-v-4-0": { - id: "cohere-embed-v-4-0", - name: "Embed v4", - family: "cohere-embed", - attachment: true, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.12, output: 0 }, - limit: { context: 128000, output: 1536 }, - }, - "deepseek-v3.2": { - id: "deepseek-v3.2", - name: "DeepSeek-V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.58, output: 1.68 }, - limit: { context: 128000, output: 128000 }, - }, - "mistral-nemo": { - id: "mistral-nemo", - name: "Mistral Nemo", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 128000, output: 128000 }, - }, - "gpt-4-turbo": { - id: "gpt-4-turbo", - name: "GPT-4 Turbo", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-11", - release_date: "2023-11-06", - last_updated: "2024-04-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 30 }, - limit: { context: 128000, output: 4096 }, - }, - "gpt-4.1": { - id: "gpt-4.1", - name: "GPT-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-05", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 1047576, output: 32768 }, - }, - "model-router": { - id: "model-router", - name: "Model Router", - family: "model-router", - attachment: true, - reasoning: false, - tool_call: true, - release_date: "2025-05-19", - last_updated: "2025-11-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "deepseek-v3-0324": { - id: "deepseek-v3-0324", - name: "DeepSeek-V3-0324", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-03-24", - last_updated: "2025-03-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.14, output: 4.56 }, - limit: { context: 131072, output: 131072 }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-06", - last_updated: "2026-02-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3 }, - limit: { context: 262144, output: 262144 }, - provider: { - npm: "@ai-sdk/openai-compatible", - api: "https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/models", - shape: "completions", - }, - }, - "gpt-5.2": { - id: "gpt-5.2", - name: "GPT-5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-5.1-codex-mini": { - id: "gpt-5.1-codex-mini", - name: "GPT-5.1 Codex Mini", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.025 }, - limit: { context: 400000, output: 128000 }, - }, - "text-embedding-3-large": { - id: "text-embedding-3-large", - name: "text-embedding-3-large", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2024-01-25", - last_updated: "2024-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.13, output: 0 }, - limit: { context: 8191, output: 3072 }, - }, - "gpt-3.5-turbo-0613": { - id: "gpt-3.5-turbo-0613", - name: "GPT-3.5 Turbo 0613", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2021-08", - release_date: "2023-06-13", - last_updated: "2023-06-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 4 }, - limit: { context: 16384, output: 16384 }, - }, - "cohere-command-r-08-2024": { - id: "cohere-command-r-08-2024", - name: "Command R", - family: "command-r", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06-01", - release_date: "2024-08-30", - last_updated: "2024-08-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 4000 }, - }, - "gpt-4.1-nano": { - id: "gpt-4.1-nano", - name: "GPT-4.1 nano", - family: "gpt-nano", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-05", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.03 }, - limit: { context: 1047576, output: 32768 }, - }, - "deepseek-v3.2-speciale": { - id: "deepseek-v3.2-speciale", - name: "DeepSeek-V3.2-Speciale", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.58, output: 1.68 }, - limit: { context: 128000, output: 128000 }, - }, - "phi-4-mini": { - id: "phi-4-mini", - name: "Phi-4-mini", - family: "phi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 128000, output: 4096 }, - }, - "deepseek-r1": { - id: "deepseek-r1", - name: "DeepSeek-R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-07", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.35, output: 5.4 }, - limit: { context: 163840, output: 163840 }, - }, - "text-embedding-3-small": { - id: "text-embedding-3-small", - name: "text-embedding-3-small", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2024-01-25", - last_updated: "2024-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0 }, - limit: { context: 8191, output: 1536 }, - }, - "gpt-3.5-turbo-0301": { - id: "gpt-3.5-turbo-0301", - name: "GPT-3.5 Turbo 0301", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2021-08", - release_date: "2023-03-01", - last_updated: "2023-03-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 2 }, - limit: { context: 4096, output: 4096 }, - }, - "deepseek-r1-0528": { - id: "deepseek-r1-0528", - name: "DeepSeek-R1-0528", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.35, output: 5.4 }, - limit: { context: 163840, output: 163840 }, - }, - "meta-llama-3-70b-instruct": { - id: "meta-llama-3-70b-instruct", - name: "Meta-Llama-3-70B-Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-12", - release_date: "2024-04-18", - last_updated: "2024-04-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.68, output: 3.54 }, - limit: { context: 8192, output: 2048 }, - }, - "llama-3.2-11b-vision-instruct": { - id: "llama-3.2-11b-vision-instruct", - name: "Llama-3.2-11B-Vision-Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.37, output: 0.37 }, - limit: { context: 128000, output: 8192 }, - }, - o3: { - id: "o3", - name: "o3", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 200000, output: 100000 }, - }, - "meta-llama-3-8b-instruct": { - id: "meta-llama-3-8b-instruct", - name: "Meta-Llama-3-8B-Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-12", - release_date: "2024-04-18", - last_updated: "2024-04-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.61 }, - limit: { context: 8192, output: 2048 }, - }, - "gpt-5.1-chat": { - id: "gpt-5.1-chat", - name: "GPT-5.1 Chat", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text", "image", "audio"], output: ["text", "image", "audio"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 128000, output: 16384 }, - }, - "grok-4": { - id: "grok-4", - name: "Grok 4", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, reasoning: 15, cache_read: 0.75 }, - limit: { context: 256000, output: 64000 }, - }, - "gpt-5-chat": { - id: "gpt-5-chat", - name: "GPT-5 Chat", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: false, - temperature: false, - knowledge: "2024-10-24", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 128000, output: 16384 }, - }, - "gpt-5.2-chat": { - id: "gpt-5.2-chat", - name: "GPT-5.2 Chat", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 128000, output: 16384 }, - }, - "cohere-command-r-plus-08-2024": { - id: "cohere-command-r-plus-08-2024", - name: "Command R+", - family: "command-r", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06-01", - release_date: "2024-08-30", - last_updated: "2024-08-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.5, output: 10 }, - limit: { context: 128000, output: 4000 }, - }, - "meta-llama-3.1-405b-instruct": { - id: "meta-llama-3.1-405b-instruct", - name: "Meta-Llama-3.1-405B-Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 5.33, output: 16 }, - limit: { context: 128000, output: 32768 }, - }, - "llama-4-scout-17b-16e-instruct": { - id: "llama-4-scout-17b-16e-instruct", - name: "Llama 4 Scout 17B 16E Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.78 }, - limit: { context: 128000, output: 8192 }, - }, - "gpt-5.1": { - id: "gpt-5.1", - name: "GPT-5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text", "image", "audio"], output: ["text", "image", "audio"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 272000, output: 128000 }, - }, - o1: { - id: "o1", - name: "o1", - family: "o", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2023-09", - release_date: "2024-12-05", - last_updated: "2024-12-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 60, cache_read: 7.5 }, - limit: { context: 200000, output: 100000 }, - }, - "deepseek-v3.1": { - id: "deepseek-v3.1", - name: "DeepSeek-V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.56, output: 1.68 }, - limit: { context: 131072, output: 131072 }, - }, - "mistral-medium-2505": { - id: "mistral-medium-2505", - name: "Mistral Medium 3", - family: "mistral-medium", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-07", - last_updated: "2025-05-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 128000, output: 128000 }, - }, - "cohere-command-a": { - id: "cohere-command-a", - name: "Command A", - family: "command-a", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06-01", - release_date: "2025-03-13", - last_updated: "2025-03-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.5, output: 10 }, - limit: { context: 256000, output: 8000 }, - }, - "phi-3.5-mini-instruct": { - id: "phi-3.5-mini-instruct", - name: "Phi-3.5-mini-instruct", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-08-20", - last_updated: "2024-08-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.52 }, - limit: { context: 128000, output: 4096 }, - }, - "llama-3.3-70b-instruct": { - id: "llama-3.3-70b-instruct", - name: "Llama-3.3-70B-Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.71, output: 0.71 }, - limit: { context: 128000, output: 32768 }, - }, - "grok-code-fast-1": { - id: "grok-code-fast-1", - name: "Grok Code Fast 1", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2025-08-28", - last_updated: "2025-08-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.5, cache_read: 0.02 }, - limit: { context: 256000, output: 10000 }, - }, - "llama-3.2-90b-vision-instruct": { - id: "llama-3.2-90b-vision-instruct", - name: "Llama-3.2-90B-Vision-Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 2.04, output: 2.04 }, - limit: { context: 128000, output: 8192 }, - }, - "grok-3": { - id: "grok-3", - name: "Grok 3", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75 }, - limit: { context: 131072, output: 8192 }, - }, - "gpt-5.2-codex": { - id: "gpt-5.2-codex", - name: "GPT-5.2 Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-01-14", - last_updated: "2026-01-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "ministral-3b": { - id: "ministral-3b", - name: "Ministral 3B", - family: "ministral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.04 }, - limit: { context: 128000, output: 8192 }, - }, - "gpt-4-turbo-vision": { - id: "gpt-4-turbo-vision", - name: "GPT-4 Turbo Vision", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-11", - release_date: "2023-11-06", - last_updated: "2024-04-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 30 }, - limit: { context: 128000, output: 4096 }, - }, - "phi-3.5-moe-instruct": { - id: "phi-3.5-moe-instruct", - name: "Phi-3.5-MoE-instruct", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-08-20", - last_updated: "2024-08-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.16, output: 0.64 }, - limit: { context: 128000, output: 4096 }, - }, - "mai-ds-r1": { - id: "mai-ds-r1", - name: "MAI-DS-R1", - family: "mai", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-06", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.35, output: 5.4 }, - limit: { context: 128000, output: 8192 }, - }, - "phi-4-multimodal": { - id: "phi-4-multimodal", - name: "Phi-4-multimodal", - family: "phi", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: true, - cost: { input: 0.08, output: 0.32, input_audio: 4 }, - limit: { context: 128000, output: 4096 }, - }, - "phi-3-medium-128k-instruct": { - id: "phi-3-medium-128k-instruct", - name: "Phi-3-medium-instruct (128k)", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.17, output: 0.68 }, - limit: { context: 128000, output: 4096 }, - }, - "grok-4-fast-reasoning": { - id: "grok-4-fast-reasoning", - name: "Grok 4 Fast (Reasoning)", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-09-19", - last_updated: "2025-09-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "text-embedding-ada-002": { - id: "text-embedding-ada-002", - name: "text-embedding-ada-002", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2022-12-15", - last_updated: "2022-12-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "gpt-4o-mini": { - id: "gpt-4o-mini", - name: "GPT-4o mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.08 }, - limit: { context: 128000, output: 16384 }, - }, - "phi-3-small-128k-instruct": { - id: "phi-3-small-128k-instruct", - name: "Phi-3-small-instruct (128k)", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 4096 }, - }, - "gpt-5-pro": { - id: "gpt-5-pro", - name: "GPT-5 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-10-06", - last_updated: "2025-10-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 120 }, - limit: { context: 400000, output: 272000 }, - }, - "gpt-5-codex": { - id: "gpt-5-codex", - name: "GPT-5-Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-5.3-codex": { - id: "gpt-5.3-codex", - name: "GPT-5.3 Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-02-24", - last_updated: "2026-02-24", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - }, - }, - alibaba: { - id: "alibaba", - env: ["DASHSCOPE_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", - name: "Alibaba", - doc: "https://www.alibabacloud.com/help/en/model-studio/models", - models: { - "qwen-vl-plus": { - id: "qwen-vl-plus", - name: "Qwen-VL Plus", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-01-25", - last_updated: "2025-08-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.21, output: 0.63 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen-vl-max": { - id: "qwen-vl-max", - name: "Qwen-VL Max", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-04-08", - last_updated: "2025-08-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 3.2 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-next-80b-a3b-thinking": { - id: "qwen3-next-80b-a3b-thinking", - name: "Qwen3-Next 80B-A3B (Thinking)", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09", - last_updated: "2025-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 6 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen3-coder-480b-a35b-instruct": { - id: "qwen3-coder-480b-a35b-instruct", - name: "Qwen3-Coder 480B-A35B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.5, output: 7.5 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen3-14b": { - id: "qwen3-14b", - name: "Qwen3 14B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.35, output: 1.4, reasoning: 4.2 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-coder-flash": { - id: "qwen3-coder-flash", - name: "Qwen3 Coder Flash", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.5 }, - limit: { context: 1000000, output: 65536 }, - }, - "qwen3-vl-30b-a3b": { - id: "qwen3-vl-30b-a3b", - name: "Qwen3-VL 30B-A3B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.8, reasoning: 2.4 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen3-asr-flash": { - id: "qwen3-asr-flash", - name: "Qwen3-ASR Flash", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2024-04", - release_date: "2025-09-08", - last_updated: "2025-09-08", - modalities: { input: ["audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.035, output: 0.035 }, - limit: { context: 53248, output: 4096 }, - }, - "qwen-max": { - id: "qwen-max", - name: "Qwen Max", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-04-03", - last_updated: "2025-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.6, output: 6.4 }, - limit: { context: 32768, output: 8192 }, - }, - "qwen-turbo": { - id: "qwen-turbo", - name: "Qwen Turbo", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-11-01", - last_updated: "2025-04-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.2, reasoning: 0.5 }, - limit: { context: 1000000, output: 16384 }, - }, - "qwen2-5-7b-instruct": { - id: "qwen2-5-7b-instruct", - name: "Qwen2.5 7B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.175, output: 0.7 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen2-5-vl-72b-instruct": { - id: "qwen2-5-vl-72b-instruct", - name: "Qwen2.5-VL 72B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 2.8, output: 8.4 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen2-5-14b-instruct": { - id: "qwen2-5-14b-instruct", - name: "Qwen2.5 14B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.35, output: 1.4 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-8b": { - id: "qwen3-8b", - name: "Qwen3 8B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.18, output: 0.7, reasoning: 2.1 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-32b": { - id: "qwen3-32b", - name: "Qwen3 32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 2.8, reasoning: 8.4 }, - limit: { context: 131072, output: 16384 }, - }, - "qwen3.5-397b-a17b": { - id: "qwen3.5-397b-a17b", - name: "Qwen3.5 397B-A17B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-16", - last_updated: "2026-02-16", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3.6, reasoning: 3.6 }, - limit: { context: 262144, output: 65536 }, - }, - "qvq-max": { - id: "qvq-max", - name: "QVQ Max", - family: "qvq", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-03-25", - last_updated: "2025-03-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.2, output: 4.8 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen2-5-omni-7b": { - id: "qwen2-5-omni-7b", - name: "Qwen2.5-Omni 7B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-12", - last_updated: "2024-12", - modalities: { input: ["text", "image", "audio", "video"], output: ["text", "audio"] }, - open_weights: true, - cost: { input: 0.1, output: 0.4, input_audio: 6.76 }, - limit: { context: 32768, output: 2048 }, - }, - "qwen2-5-vl-7b-instruct": { - id: "qwen2-5-vl-7b-instruct", - name: "Qwen2.5-VL 7B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.35, output: 1.05 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen-omni-turbo-realtime": { - id: "qwen-omni-turbo-realtime", - name: "Qwen-Omni Turbo Realtime", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-05-08", - last_updated: "2025-05-08", - modalities: { input: ["text", "image", "audio"], output: ["text", "audio"] }, - open_weights: false, - cost: { input: 0.27, output: 1.07, input_audio: 4.44, output_audio: 8.89 }, - limit: { context: 32768, output: 2048 }, - }, - "qwen3-235b-a22b": { - id: "qwen3-235b-a22b", - name: "Qwen3 235B-A22B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 2.8, reasoning: 8.4 }, - limit: { context: 131072, output: 16384 }, - }, - "qwen3-coder-30b-a3b-instruct": { - id: "qwen3-coder-30b-a3b-instruct", - name: "Qwen3-Coder 30B-A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.45, output: 2.25 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen-omni-turbo": { - id: "qwen-omni-turbo", - name: "Qwen-Omni Turbo", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-01-19", - last_updated: "2025-03-26", - modalities: { input: ["text", "image", "audio", "video"], output: ["text", "audio"] }, - open_weights: false, - cost: { input: 0.07, output: 0.27, input_audio: 4.44, output_audio: 8.89 }, - limit: { context: 32768, output: 2048 }, - }, - "qwen-mt-plus": { - id: "qwen-mt-plus", - name: "Qwen-MT Plus", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-04", - release_date: "2025-01", - last_updated: "2025-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.46, output: 7.37 }, - limit: { context: 16384, output: 8192 }, - }, - "qwen3-vl-plus": { - id: "qwen3-vl-plus", - name: "Qwen3-VL Plus", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-23", - last_updated: "2025-09-23", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.6, reasoning: 4.8 }, - limit: { context: 262144, output: 32768 }, - }, - "qwen3-livetranslate-flash-realtime": { - id: "qwen3-livetranslate-flash-realtime", - name: "Qwen3-LiveTranslate Flash Realtime", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-04", - release_date: "2025-09-22", - last_updated: "2025-09-22", - modalities: { input: ["text", "image", "audio", "video"], output: ["text", "audio"] }, - open_weights: false, - cost: { input: 10, output: 10, input_audio: 10, output_audio: 38 }, - limit: { context: 53248, output: 4096 }, - }, - "qwen-plus": { - id: "qwen-plus", - name: "Qwen Plus", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-01-25", - last_updated: "2025-09-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.2, reasoning: 4 }, - limit: { context: 1000000, output: 32768 }, - }, - "qwen2-5-32b-instruct": { - id: "qwen2-5-32b-instruct", - name: "Qwen2.5 32B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 2.8 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-next-80b-a3b-instruct": { - id: "qwen3-next-80b-a3b-instruct", - name: "Qwen3-Next 80B-A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09", - last_updated: "2025-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 2 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen3.5-plus": { - id: "qwen3.5-plus", - name: "Qwen3.5 Plus", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-16", - last_updated: "2026-02-16", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2.4, reasoning: 2.4 }, - limit: { context: 1000000, output: 65536 }, - }, - "qwen3-max": { - id: "qwen3-max", - name: "Qwen3 Max", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-23", - last_updated: "2025-09-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.2, output: 6 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen3-omni-flash": { - id: "qwen3-omni-flash", - name: "Qwen3-Omni Flash", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image", "audio", "video"], output: ["text", "audio"] }, - open_weights: false, - cost: { input: 0.43, output: 1.66, input_audio: 3.81, output_audio: 15.11 }, - limit: { context: 65536, output: 16384 }, - }, - "qwen3-coder-plus": { - id: "qwen3-coder-plus", - name: "Qwen3 Coder Plus", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 5 }, - limit: { context: 1048576, output: 65536 }, - }, - "qwen-flash": { - id: "qwen-flash", - name: "Qwen Flash", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4 }, - limit: { context: 1000000, output: 32768 }, - }, - "qwen2-5-72b-instruct": { - id: "qwen2-5-72b-instruct", - name: "Qwen2.5 72B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.4, output: 5.6 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-omni-flash-realtime": { - id: "qwen3-omni-flash-realtime", - name: "Qwen3-Omni Flash Realtime", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image", "audio", "video"], output: ["text", "audio"] }, - open_weights: false, - cost: { input: 0.52, output: 1.99, input_audio: 4.57, output_audio: 18.13 }, - limit: { context: 65536, output: 16384 }, - }, - "qwen-vl-ocr": { - id: "qwen-vl-ocr", - name: "Qwen-VL OCR", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-04", - release_date: "2024-10-28", - last_updated: "2025-04-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.72, output: 0.72 }, - limit: { context: 34096, output: 4096 }, - }, - "qwq-plus": { - id: "qwq-plus", - name: "QwQ Plus", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-03-05", - last_updated: "2025-03-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 2.4 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-vl-235b-a22b": { - id: "qwen3-vl-235b-a22b", - name: "Qwen3-VL 235B-A22B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 2.8, reasoning: 8.4 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen-plus-character-ja": { - id: "qwen-plus-character-ja", - name: "Qwen Plus Character (Japanese)", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-01", - last_updated: "2024-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.4 }, - limit: { context: 8192, output: 512 }, - }, - "qwen-mt-turbo": { - id: "qwen-mt-turbo", - name: "Qwen-MT Turbo", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-04", - release_date: "2025-01", - last_updated: "2025-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.16, output: 0.49 }, - limit: { context: 16384, output: 8192 }, - }, - }, - }, - "cloudflare-workers-ai": { - id: "cloudflare-workers-ai", - env: ["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1", - name: "Cloudflare Workers AI", - doc: "https://developers.cloudflare.com/workers-ai/models/", - models: { - "@cf/zai-org/glm-4.7-flash": { - id: "@cf/zai-org/glm-4.7-flash", - name: "GLM-4.7-Flash", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.4 }, - limit: { context: 131072, output: 131072 }, - }, - "@cf/ibm-granite/granite-4.0-h-micro": { - id: "@cf/ibm-granite/granite-4.0-h-micro", - name: "IBM Granite 4.0 H Micro", - family: "granite", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.017, output: 0.11 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/baai/bge-small-en-v1.5": { - id: "@cf/baai/bge-small-en-v1.5", - name: "BGE Small EN v1.5", - family: "bge", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/baai/bge-large-en-v1.5": { - id: "@cf/baai/bge-large-en-v1.5", - name: "BGE Large EN v1.5", - family: "bge", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/baai/bge-reranker-base": { - id: "@cf/baai/bge-reranker-base", - name: "BGE Reranker Base", - family: "bge", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-09", - last_updated: "2025-04-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.0031, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/baai/bge-m3": { - id: "@cf/baai/bge-m3", - name: "BGE M3", - family: "bge", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.012, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/baai/bge-base-en-v1.5": { - id: "@cf/baai/bge-base-en-v1.5", - name: "BGE Base EN v1.5", - family: "bge", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.067, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/pfnet/plamo-embedding-1b": { - id: "@cf/pfnet/plamo-embedding-1b", - name: "PLaMo Embedding 1B", - family: "plamo", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.019, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b": { - id: "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b", - name: "DeepSeek R1 Distill Qwen 32B", - family: "deepseek-thinking", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 4.88 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/facebook/bart-large-cnn": { - id: "@cf/facebook/bart-large-cnn", - name: "BART Large CNN", - family: "bart", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-09", - last_updated: "2025-04-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/mistral/mistral-7b-instruct-v0.1": { - id: "@cf/mistral/mistral-7b-instruct-v0.1", - name: "Mistral 7B Instruct v0.1", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.11, output: 0.19 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/myshell-ai/melotts": { - id: "@cf/myshell-ai/melotts", - name: "MyShell MeloTTS", - family: "melotts", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/pipecat-ai/smart-turn-v2": { - id: "@cf/pipecat-ai/smart-turn-v2", - name: "Pipecat Smart Turn v2", - family: "smart-turn", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/google/gemma-3-12b-it": { - id: "@cf/google/gemma-3-12b-it", - name: "Gemma 3 12B IT", - family: "gemma", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-11", - last_updated: "2025-04-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.35, output: 0.56 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/qwen/qwq-32b": { - id: "@cf/qwen/qwq-32b", - name: "QwQ 32B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-11", - last_updated: "2025-04-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.66, output: 1 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/qwen/qwen3-30b-a3b-fp8": { - id: "@cf/qwen/qwen3-30b-a3b-fp8", - name: "Qwen3 30B A3B FP8", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.051, output: 0.34 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/qwen/qwen2.5-coder-32b-instruct": { - id: "@cf/qwen/qwen2.5-coder-32b-instruct", - name: "Qwen 2.5 Coder 32B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-11", - last_updated: "2025-04-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.66, output: 1 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/qwen/qwen3-embedding-0.6b": { - id: "@cf/qwen/qwen3-embedding-0.6b", - name: "Qwen3 Embedding 0.6B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.012, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/meta/llama-3.1-8b-instruct-fp8": { - id: "@cf/meta/llama-3.1-8b-instruct-fp8", - name: "Llama 3.1 8B Instruct FP8", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.29 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/meta/llama-3-8b-instruct-awq": { - id: "@cf/meta/llama-3-8b-instruct-awq", - name: "Llama 3 8B Instruct AWQ", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.12, output: 0.27 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/meta/llama-3.1-8b-instruct-awq": { - id: "@cf/meta/llama-3.1-8b-instruct-awq", - name: "Llama 3.1 8B Instruct AWQ", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.12, output: 0.27 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/meta/llama-4-scout-17b-16e-instruct": { - id: "@cf/meta/llama-4-scout-17b-16e-instruct", - name: "Llama 4 Scout 17B 16E Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.85 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/meta/llama-3.2-11b-vision-instruct": { - id: "@cf/meta/llama-3.2-11b-vision-instruct", - name: "Llama 3.2 11B Vision Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.049, output: 0.68 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/meta/llama-3.2-3b-instruct": { - id: "@cf/meta/llama-3.2-3b-instruct", - name: "Llama 3.2 3B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.051, output: 0.34 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/meta/llama-guard-3-8b": { - id: "@cf/meta/llama-guard-3-8b", - name: "Llama Guard 3 8B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.48, output: 0.03 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/meta/llama-3.2-1b-instruct": { - id: "@cf/meta/llama-3.2-1b-instruct", - name: "Llama 3.2 1B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.027, output: 0.2 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/meta/llama-3.3-70b-instruct-fp8-fast": { - id: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", - name: "Llama 3.3 70B Instruct FP8 Fast", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 2.25 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/meta/llama-3.1-8b-instruct": { - id: "@cf/meta/llama-3.1-8b-instruct", - name: "Llama 3.1 8B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.28, output: 0.8299999999999998 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/meta/m2m100-1.2b": { - id: "@cf/meta/m2m100-1.2b", - name: "M2M100 1.2B", - family: "m2m", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.34, output: 0.34 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/meta/llama-2-7b-chat-fp16": { - id: "@cf/meta/llama-2-7b-chat-fp16", - name: "Llama 2 7B Chat FP16", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.56, output: 6.67 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/meta/llama-3-8b-instruct": { - id: "@cf/meta/llama-3-8b-instruct", - name: "Llama 3 8B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.28, output: 0.83 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/mistralai/mistral-small-3.1-24b-instruct": { - id: "@cf/mistralai/mistral-small-3.1-24b-instruct", - name: "Mistral Small 3.1 24B Instruct", - family: "mistral-small", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-11", - last_updated: "2025-04-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.35, output: 0.56 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/deepgram/aura-2-es": { - id: "@cf/deepgram/aura-2-es", - name: "Deepgram Aura 2 (ES)", - family: "aura", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/deepgram/nova-3": { - id: "@cf/deepgram/nova-3", - name: "Deepgram Nova 3", - family: "nova", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/deepgram/aura-2-en": { - id: "@cf/deepgram/aura-2-en", - name: "Deepgram Aura 2 (EN)", - family: "aura", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/openai/gpt-oss-120b": { - id: "@cf/openai/gpt-oss-120b", - name: "GPT OSS 120B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.35, output: 0.75 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/openai/gpt-oss-20b": { - id: "@cf/openai/gpt-oss-20b", - name: "GPT OSS 20B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.3 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/ai4bharat/indictrans2-en-indic-1B": { - id: "@cf/ai4bharat/indictrans2-en-indic-1B", - name: "IndicTrans2 EN-Indic 1B", - family: "indictrans", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.34, output: 0.34 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/huggingface/distilbert-sst-2-int8": { - id: "@cf/huggingface/distilbert-sst-2-int8", - name: "DistilBERT SST-2 INT8", - family: "distilbert", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.026, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "@cf/aisingapore/gemma-sea-lion-v4-27b-it": { - id: "@cf/aisingapore/gemma-sea-lion-v4-27b-it", - name: "Gemma SEA-LION v4 27B IT", - family: "gemma", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.35, output: 0.56 }, - limit: { context: 128000, output: 16384 }, - }, - }, - }, - groq: { - id: "groq", - env: ["GROQ_API_KEY"], - npm: "@ai-sdk/groq", - name: "Groq", - doc: "https://console.groq.com/docs/models", - models: { - "llama3-70b-8192": { - id: "llama3-70b-8192", - name: "Llama 3 70B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-03", - release_date: "2024-04-18", - last_updated: "2024-04-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.59, output: 0.79 }, - limit: { context: 8192, output: 8192 }, - status: "deprecated", - }, - "qwen-qwq-32b": { - id: "qwen-qwq-32b", - name: "Qwen QwQ 32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2024-11-27", - last_updated: "2024-11-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.29, output: 0.39 }, - limit: { context: 131072, output: 16384 }, - status: "deprecated", - }, - "llama-3.1-8b-instant": { - id: "llama-3.1-8b-instant", - name: "Llama 3.1 8B Instant", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.08 }, - limit: { context: 131072, output: 131072 }, - }, - "llama-guard-3-8b": { - id: "llama-guard-3-8b", - name: "Llama Guard 3 8B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 8192, output: 8192 }, - status: "deprecated", - }, - "deepseek-r1-distill-llama-70b": { - id: "deepseek-r1-distill-llama-70b", - name: "DeepSeek R1 Distill Llama 70B", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.75, output: 0.99 }, - limit: { context: 131072, output: 8192 }, - status: "deprecated", - }, - "llama3-8b-8192": { - id: "llama3-8b-8192", - name: "Llama 3 8B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-03", - release_date: "2024-04-18", - last_updated: "2024-04-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.08 }, - limit: { context: 8192, output: 8192 }, - status: "deprecated", - }, - "mistral-saba-24b": { - id: "mistral-saba-24b", - name: "Mistral Saba 24B", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-02-06", - last_updated: "2025-02-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.79, output: 0.79 }, - limit: { context: 32768, output: 32768 }, - status: "deprecated", - }, - "llama-3.3-70b-versatile": { - id: "llama-3.3-70b-versatile", - name: "Llama 3.3 70B Versatile", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.59, output: 0.79 }, - limit: { context: 131072, output: 32768 }, - }, - "gemma2-9b-it": { - id: "gemma2-9b-it", - name: "Gemma 2 9B", - family: "gemma", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2024-06-27", - last_updated: "2024-06-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 8192, output: 8192 }, - status: "deprecated", - }, - "moonshotai/kimi-k2-instruct": { - id: "moonshotai/kimi-k2-instruct", - name: "Kimi K2 Instruct", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-14", - last_updated: "2025-07-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3 }, - limit: { context: 131072, output: 16384 }, - status: "deprecated", - }, - "moonshotai/kimi-k2-instruct-0905": { - id: "moonshotai/kimi-k2-instruct-0905", - name: "Kimi K2 Instruct 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3 }, - limit: { context: 262144, output: 16384 }, - }, - "qwen/qwen3-32b": { - id: "qwen/qwen3-32b", - name: "Qwen3 32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-11-08", - release_date: "2024-12-23", - last_updated: "2024-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.29, output: 0.59 }, - limit: { context: 131072, output: 16384 }, - }, - "meta-llama/llama-4-scout-17b-16e-instruct": { - id: "meta-llama/llama-4-scout-17b-16e-instruct", - name: "Llama 4 Scout 17B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.11, output: 0.34 }, - limit: { context: 131072, output: 8192 }, - }, - "meta-llama/llama-guard-4-12b": { - id: "meta-llama/llama-guard-4-12b", - name: "Llama Guard 4 12B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 131072, output: 1024 }, - }, - "meta-llama/llama-4-maverick-17b-128e-instruct": { - id: "meta-llama/llama-4-maverick-17b-128e-instruct", - name: "Llama 4 Maverick 17B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 131072, output: 8192 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 131072, output: 65536 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "GPT OSS 20B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 131072, output: 65536 }, - }, - }, - }, - wandb: { - id: "wandb", - env: ["WANDB_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.inference.wandb.ai/v1", - name: "Weights & Biases", - doc: "https://docs.wandb.ai/guides/integrations/inference/", - models: { - "zai-org/GLM-5-FP8": { - id: "zai-org/GLM-5-FP8", - name: "GLM 5", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2 }, - limit: { context: 200000, output: 200000 }, - }, - "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-FP8": { - id: "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-FP8", - name: "NVIDIA Nemotron 3 Super 120B", - family: "nemotron", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-03-11", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 262144, output: 262144 }, - }, - "microsoft/Phi-4-mini-instruct": { - id: "microsoft/Phi-4-mini-instruct", - name: "Phi-4-mini-instruct", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.08, output: 0.35 }, - limit: { context: 128000, output: 128000 }, - }, - "MiniMaxAI/MiniMax-M2.5": { - id: "MiniMaxAI/MiniMax-M2.5", - name: "MiniMax M2.5", - family: "minimax", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 196608, output: 196608 }, - }, - "deepseek-ai/DeepSeek-V3.1": { - id: "deepseek-ai/DeepSeek-V3.1", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-21", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 1.65 }, - limit: { context: 161000, output: 161000 }, - }, - "moonshotai/Kimi-K2.5": { - id: "moonshotai/Kimi-K2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-01-27", - last_updated: "2026-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 2.85 }, - limit: { context: 262144, output: 262144 }, - }, - "meta-llama/Llama-4-Scout-17B-16E-Instruct": { - id: "meta-llama/Llama-4-Scout-17B-16E-Instruct", - name: "Llama 4 Scout 17B 16E Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-31", - last_updated: "2026-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.17, output: 0.66 }, - limit: { context: 64000, output: 64000 }, - }, - "meta-llama/Llama-3.1-70B-Instruct": { - id: "meta-llama/Llama-3.1-70B-Instruct", - name: "Llama 3.1 70B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-07-23", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.8, output: 0.8 }, - limit: { context: 128000, output: 128000 }, - }, - "meta-llama/Llama-3.1-8B-Instruct": { - id: "meta-llama/Llama-3.1-8B-Instruct", - name: "Meta-Llama-3.1-8B-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.22, output: 0.22 }, - limit: { context: 128000, output: 128000 }, - }, - "meta-llama/Llama-3.3-70B-Instruct": { - id: "meta-llama/Llama-3.3-70B-Instruct", - name: "Llama-3.3-70B-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-06", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.71, output: 0.71 }, - limit: { context: 128000, output: 128000 }, - }, - "Qwen/Qwen3-30B-A3B-Instruct-2507": { - id: "Qwen/Qwen3-30B-A3B-Instruct-2507", - name: "Qwen3 30B A3B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-29", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 262144, output: 262144 }, - }, - "Qwen/Qwen3-235B-A22B-Thinking-2507": { - id: "Qwen/Qwen3-235B-A22B-Thinking-2507", - name: "Qwen3-235B-A22B-Thinking-2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-25", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 262144, output: 262144 }, - }, - "Qwen/Qwen3-Coder-480B-A35B-Instruct": { - id: "Qwen/Qwen3-Coder-480B-A35B-Instruct", - name: "Qwen3-Coder-480B-A35B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 1.5 }, - limit: { context: 262144, output: 262144 }, - }, - "Qwen/Qwen3-235B-A22B-Instruct-2507": { - id: "Qwen/Qwen3-235B-A22B-Instruct-2507", - name: "Qwen3 235B A22B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-28", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 262144, output: 262144 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "gpt-oss-120b", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 131072, output: 131072 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "gpt-oss-20b", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.2 }, - limit: { context: 131072, output: 131072 }, - }, - "OpenPipe/Qwen3-14B-Instruct": { - id: "OpenPipe/Qwen3-14B-Instruct", - name: "OpenPipe Qwen3 14B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-29", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.22 }, - limit: { context: 32768, output: 32768 }, - }, - }, - }, - aihubmix: { - id: "aihubmix", - env: ["AIHUBMIX_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://aihubmix.com/v1", - name: "AIHubMix", - doc: "https://docs.aihubmix.com", - models: { - "qwen3-235b-a22b-instruct-2507": { - id: "qwen3-235b-a22b-instruct-2507", - name: "Qwen3 235B A22B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-30", - last_updated: "2025-07-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 1.12 }, - limit: { context: 262144, output: 262144 }, - }, - "gpt-5-codex": { - id: "gpt-5-codex", - name: "GPT-5-Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-5-pro": { - id: "gpt-5-pro", - name: "GPT-5-Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 7, output: 28, cache_read: 3.5 }, - limit: { context: 400000, output: 128000 }, - }, - "glm-5": { - id: "glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.88, output: 2.82 }, - limit: { context: 204800, output: 131072 }, - }, - "gpt-5.1-codex-max": { - id: "gpt-5.1-codex-max", - name: "GPT-5.1-Codex-Max", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "claude-opus-4-1": { - id: "claude-opus-4-1", - name: "Claude Opus 4.1", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 16.5, output: 82.5, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "qwen3-coder-480b-a35b-instruct": { - id: "qwen3-coder-480b-a35b-instruct", - name: "Qwen3 Coder 480B A35B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-08-01", - last_updated: "2025-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.82, output: 3.29 }, - limit: { context: 262144, output: 131000 }, - }, - "gpt-5.2-codex": { - id: "gpt-5.2-codex", - name: "GPT-5.2-Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-08-31", - release_date: "2026-01-14", - last_updated: "2026-01-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "claude-opus-4-6": { - id: "claude-opus-4-6", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 5, - output: 25, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 200000, output: 128000 }, - }, - "coding-glm-4.7-free": { - id: "coding-glm-4.7-free", - name: "Coding GLM 4.7 Free", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "coding-minimax-m2.1-free": { - id: "coding-minimax-m2.1-free", - name: "Coding MiniMax M2.1 Free", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "claude-sonnet-4-6": { - id: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 200000, output: 64000 }, - }, - "gemini-2.5-flash": { - id: "gemini-2.5-flash", - name: "Gemini 2.5 Flash", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.075, output: 0.3, cache_read: 0.02 }, - limit: { context: 1000000, output: 65000 }, - }, - "claude-opus-4-6-think": { - id: "claude-opus-4-6-think", - name: "Claude Opus 4.6 Think", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 5, - output: 25, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 200000, output: 128000 }, - }, - "gpt-5.1": { - id: "gpt-5.1", - name: "GPT-5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-15", - last_updated: "2025-11-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "qwen3-coder-next": { - id: "qwen3-coder-next", - name: "Qwen3 Coder Next", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-02-04", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.14, output: 0.55 }, - limit: { context: 262144, input: 262144, output: 65536 }, - }, - "minimax-m2.1": { - id: "minimax-m2.1", - name: "MiniMax M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.29, output: 1.15 }, - limit: { context: 204800, output: 131072 }, - }, - "gpt-4.1-nano": { - id: "gpt-4.1-nano", - name: "GPT-4.1 nano", - family: "gpt-nano", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.03 }, - limit: { context: 1047576, output: 32768 }, - }, - "gemini-3-pro-preview-search": { - id: "gemini-3-pro-preview-search", - name: "Gemini 3 Pro Preview Search", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.5 }, - limit: { context: 1000000, output: 65000 }, - }, - "gpt-5.1-codex-mini": { - id: "gpt-5.1-codex-mini", - name: "GPT-5.1 Codex Mini", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-15", - last_updated: "2025-11-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.03 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-5.2": { - id: "gpt-5.2", - name: "GPT-5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "deepseek-v3.2-think": { - id: "deepseek-v3.2-think", - name: "DeepSeek-V3.2-Think", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.45 }, - limit: { context: 131000, output: 64000 }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-07", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3, cache_read: 0.1 }, - limit: { context: 262144, output: 262144 }, - }, - "Kimi-K2-0905": { - id: "Kimi-K2-0905", - name: "Kimi K2 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.19 }, - limit: { context: 262144, output: 262144 }, - }, - "gpt-4.1": { - id: "gpt-4.1", - name: "GPT-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 1047576, output: 32768 }, - }, - "deepseek-v3.2": { - id: "deepseek-v3.2", - name: "DeepSeek-V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.45 }, - limit: { context: 131000, output: 64000 }, - }, - "qwen3-max-2026-01-23": { - id: "qwen3-max-2026-01-23", - name: "Qwen3 Max", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-23", - last_updated: "2025-09-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.34, output: 1.37 }, - limit: { context: 262144, output: 65536 }, - }, - "gpt-5": { - id: "gpt-5", - name: "GPT-5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 20, cache_read: 2.5 }, - limit: { context: 400000, output: 128000 }, - }, - "o4-mini": { - id: "o4-mini", - name: "o4-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: false, - temperature: false, - knowledge: "2024-09", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 6, cache_read: 0.75 }, - limit: { context: 200000, output: 65536 }, - }, - "gpt-4.1-mini": { - id: "gpt-4.1-mini", - name: "GPT-4.1 mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.6, cache_read: 0.1 }, - limit: { context: 1047576, output: 32768 }, - }, - "glm-4.7": { - id: "glm-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 1.1, cache_read: 0.548 }, - limit: { context: 204800, output: 131072 }, - }, - "claude-haiku-4-5": { - id: "claude-haiku-4-5", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 5.5, cache_read: 0.11, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "gpt-5.1-codex": { - id: "gpt-5.1-codex", - name: "GPT-5.1 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-15", - last_updated: "2025-11-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 400000, output: 128000 }, - }, - "minimax-m2.5": { - id: "minimax-m2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.29, output: 1.15 }, - limit: { context: 204800, output: 131072 }, - }, - "claude-opus-4-5": { - id: "claude-opus-4-5", - name: "Claude Opus 4.5", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-11-25", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 32000 }, - }, - "qwen3-235b-a22b-thinking-2507": { - id: "qwen3-235b-a22b-thinking-2507", - name: "Qwen3 235B A22B Thinking 2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-30", - last_updated: "2025-07-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 2.8 }, - limit: { context: 262144, output: 262144 }, - }, - "gemini-3-pro-preview": { - id: "gemini-3-pro-preview", - name: "Gemini 3 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.5 }, - limit: { context: 1000000, output: 65000 }, - }, - "qwen3.5-plus": { - id: "qwen3.5-plus", - name: "Qwen 3.5 Plus", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-16", - last_updated: "2026-02-16", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.11, output: 0.66 }, - limit: { context: 1000000, output: 65536 }, - }, - "claude-sonnet-4-5": { - id: "claude-sonnet-4-5", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3.3, output: 16.5, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - name: "GPT-5-Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 6, cache_read: 0.75 }, - limit: { context: 200000, output: 64000 }, - }, - "deepseek-v3.2-fast": { - id: "deepseek-v3.2-fast", - name: "DeepSeek-V3.2-Fast", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: false, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.1, output: 3.29 }, - limit: { context: 128000, output: 128000 }, - }, - "glm-4.6v": { - id: "glm-4.6v", - name: "GLM-4.6V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.14, output: 0.41 }, - limit: { context: 128000, output: 32768 }, - }, - "coding-glm-4.7": { - id: "coding-glm-4.7", - name: "Coding-GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 1.1, cache_read: 0.548 }, - limit: { context: 204800, output: 131072 }, - }, - "coding-glm-5-free": { - id: "coding-glm-5-free", - name: "Coding-GLM-5-Free", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 5, cache_read: 0.31 }, - limit: { context: 2000000, output: 65000 }, - }, - "gpt-5-nano": { - id: "gpt-5-nano", - name: "GPT-5-Nano", - family: "gpt-nano", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 2, cache_read: 0.25 }, - limit: { context: 128000, output: 16384 }, - }, - "claude-sonnet-4-6-think": { - id: "claude-sonnet-4-6-think", - name: "Claude Sonnet 4.6 Think", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 200000, output: 64000 }, - }, - "gpt-4o": { - id: "gpt-4o", - name: "GPT-4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-05-13", - last_updated: "2024-08-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10, cache_read: 1.25 }, - limit: { context: 128000, output: 16384 }, - }, - }, - }, - "minimax-coding-plan": { - id: "minimax-coding-plan", - env: ["MINIMAX_API_KEY"], - npm: "@ai-sdk/anthropic", - api: "https://api.minimax.io/anthropic/v1", - name: "MiniMax Coding Plan (minimax.io)", - doc: "https://platform.minimax.io/docs/coding-plan/intro", - models: { - "MiniMax-M2.5": { - id: "MiniMax-M2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "MiniMax-M2": { - id: "MiniMax-M2", - name: "MiniMax-M2", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-10-27", - last_updated: "2025-10-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 196608, output: 128000 }, - }, - "MiniMax-M2.5-highspeed": { - id: "MiniMax-M2.5-highspeed", - name: "MiniMax-M2.5-highspeed", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-13", - last_updated: "2026-02-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "MiniMax-M2.1": { - id: "MiniMax-M2.1", - name: "MiniMax-M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 204800, output: 131072 }, - }, - }, - }, - "kimi-for-coding": { - id: "kimi-for-coding", - env: ["KIMI_API_KEY"], - npm: "@ai-sdk/anthropic", - api: "https://api.kimi.com/coding/v1", - name: "Kimi For Coding", - doc: "https://www.kimi.com/coding/docs/en/third-party-agents.html", - models: { - k2p5: { - id: "k2p5", - name: "Kimi K2.5", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 262144, output: 32768 }, - }, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-11", - last_updated: "2025-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 262144, output: 32768 }, - }, - }, - }, - mistral: { - id: "mistral", - env: ["MISTRAL_API_KEY"], - npm: "@ai-sdk/mistral", - name: "Mistral", - doc: "https://docs.mistral.ai/getting-started/models/", - models: { - "devstral-medium-2507": { - id: "devstral-medium-2507", - name: "Devstral Medium", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-07-10", - last_updated: "2025-07-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2 }, - limit: { context: 128000, output: 128000 }, - }, - "labs-devstral-small-2512": { - id: "labs-devstral-small-2512", - name: "Devstral Small 2", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-12", - release_date: "2025-12-09", - last_updated: "2025-12-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 256000 }, - }, - "devstral-medium-latest": { - id: "devstral-medium-latest", - name: "Devstral 2 (latest)", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-12", - release_date: "2025-12-02", - last_updated: "2025-12-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2 }, - limit: { context: 262144, output: 262144 }, - }, - "open-mistral-7b": { - id: "open-mistral-7b", - name: "Mistral 7B", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2023-09-27", - last_updated: "2023-09-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 0.25 }, - limit: { context: 8000, output: 8000 }, - }, - "mistral-small-2506": { - id: "mistral-small-2506", - name: "Mistral Small 3.2", - family: "mistral-small", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-06-20", - last_updated: "2025-06-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 128000, output: 16384 }, - }, - "mistral-medium-2505": { - id: "mistral-medium-2505", - name: "Mistral Medium 3", - family: "mistral-medium", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-07", - last_updated: "2025-05-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 131072, output: 131072 }, - }, - "codestral-latest": { - id: "codestral-latest", - name: "Codestral (latest)", - family: "codestral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-05-29", - last_updated: "2025-01-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 256000, output: 4096 }, - }, - "ministral-8b-latest": { - id: "ministral-8b-latest", - name: "Ministral 8B (latest)", - family: "ministral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-10-01", - last_updated: "2024-10-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 128000, output: 128000 }, - }, - "magistral-small": { - id: "magistral-small", - name: "Magistral Small", - family: "magistral-small", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-03-17", - last_updated: "2025-03-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 128000, output: 128000 }, - }, - "mistral-large-2512": { - id: "mistral-large-2512", - name: "Mistral Large 3", - family: "mistral-large", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2024-11-01", - last_updated: "2025-12-02", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 262144, output: 262144 }, - }, - "ministral-3b-latest": { - id: "ministral-3b-latest", - name: "Ministral 3B (latest)", - family: "ministral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-10-01", - last_updated: "2024-10-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.04 }, - limit: { context: 128000, output: 128000 }, - }, - "mistral-embed": { - id: "mistral-embed", - name: "Mistral Embed", - family: "mistral-embed", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2023-12-11", - last_updated: "2023-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0 }, - limit: { context: 8000, output: 3072 }, - }, - "devstral-small-2505": { - id: "devstral-small-2505", - name: "Devstral Small 2505", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-07", - last_updated: "2025-05-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 128000, output: 128000 }, - }, - "pixtral-12b": { - id: "pixtral-12b", - name: "Pixtral 12B", - family: "pixtral", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2024-09-01", - last_updated: "2024-09-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 128000, output: 128000 }, - }, - "open-mixtral-8x7b": { - id: "open-mixtral-8x7b", - name: "Mixtral 8x7B", - family: "mixtral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-01", - release_date: "2023-12-11", - last_updated: "2023-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 0.7 }, - limit: { context: 32000, output: 32000 }, - }, - "pixtral-large-latest": { - id: "pixtral-large-latest", - name: "Pixtral Large (latest)", - family: "pixtral", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2024-11-01", - last_updated: "2024-11-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 6 }, - limit: { context: 128000, output: 128000 }, - }, - "mistral-nemo": { - id: "mistral-nemo", - name: "Mistral Nemo", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 128000, output: 128000 }, - }, - "devstral-2512": { - id: "devstral-2512", - name: "Devstral 2", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-12", - release_date: "2025-12-09", - last_updated: "2025-12-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2 }, - limit: { context: 262144, output: 262144 }, - }, - "mistral-large-latest": { - id: "mistral-large-latest", - name: "Mistral Large (latest)", - family: "mistral-large", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2024-11-01", - last_updated: "2025-12-02", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 262144, output: 262144 }, - }, - "mistral-medium-2508": { - id: "mistral-medium-2508", - name: "Mistral Medium 3.1", - family: "mistral-medium", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-08-12", - last_updated: "2025-08-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 262144, output: 262144 }, - }, - "mistral-large-2411": { - id: "mistral-large-2411", - name: "Mistral Large 2.1", - family: "mistral-large", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2024-11-01", - last_updated: "2024-11-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 6 }, - limit: { context: 131072, output: 16384 }, - }, - "mistral-small-latest": { - id: "mistral-small-latest", - name: "Mistral Small (latest)", - family: "mistral-small", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2024-09-01", - last_updated: "2024-09-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 128000, output: 16384 }, - }, - "open-mixtral-8x22b": { - id: "open-mixtral-8x22b", - name: "Mixtral 8x22B", - family: "mixtral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-04-17", - last_updated: "2024-04-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 6 }, - limit: { context: 64000, output: 64000 }, - }, - "mistral-medium-latest": { - id: "mistral-medium-latest", - name: "Mistral Medium (latest)", - family: "mistral-medium", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-07", - last_updated: "2025-05-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2 }, - limit: { context: 128000, output: 16384 }, - }, - "devstral-small-2507": { - id: "devstral-small-2507", - name: "Devstral Small", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-07-10", - last_updated: "2025-07-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 128000, output: 128000 }, - }, - "magistral-medium-latest": { - id: "magistral-medium-latest", - name: "Magistral Medium (latest)", - family: "magistral-medium", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-03-17", - last_updated: "2025-03-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 5 }, - limit: { context: 128000, output: 16384 }, - }, - }, - }, - abacus: { - id: "abacus", - env: ["ABACUS_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://routellm.abacus.ai/v1", - name: "Abacus", - doc: "https://abacus.ai/help/api", - models: { - "gpt-4o-2024-11-20": { - id: "gpt-4o-2024-11-20", - name: "GPT-4o (2024-11-20)", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-11-20", - last_updated: "2024-11-20", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 128000, output: 16384 }, - }, - "gpt-5.3-codex": { - id: "gpt-5.3-codex", - name: "GPT-5.3 Codex", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "gpt-5-codex": { - id: "gpt-5-codex", - name: "GPT-5 Codex", - family: "gpt", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "claude-opus-4-5-20251101": { - id: "claude-opus-4-5-20251101", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-01", - last_updated: "2025-11-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25 }, - limit: { context: 200000, output: 64000 }, - }, - "gpt-4o-mini": { - id: "gpt-4o-mini", - name: "GPT-4o Mini", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 16384 }, - }, - "gpt-5.1-codex-max": { - id: "gpt-5.1-codex-max", - name: "GPT-5.1 Codex Max", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "gpt-5.2-chat-latest": { - id: "gpt-5.2-chat-latest", - name: "GPT-5.2 Chat Latest", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2026-01-01", - last_updated: "2026-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 400000, output: 128000 }, - }, - "grok-4-0709": { - id: "grok-4-0709", - name: "Grok 4", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 256000, output: 16384 }, - }, - "gpt-5.2-codex": { - id: "gpt-5.2-codex", - name: "GPT-5.2 Codex", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "claude-opus-4-6": { - id: "claude-opus-4-6", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25 }, - limit: { context: 200000, output: 128000 }, - }, - "grok-code-fast-1": { - id: "grok-code-fast-1", - name: "Grok Code Fast 1", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-09-01", - last_updated: "2025-09-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.5 }, - limit: { context: 256000, output: 16384 }, - }, - "claude-sonnet-4-6": { - id: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, output: 64000 }, - }, - "gemini-2.5-flash": { - id: "gemini-2.5-flash", - name: "Gemini 2.5 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5 }, - limit: { context: 1048576, output: 65536 }, - }, - "gpt-5.3-codex-xhigh": { - id: "gpt-5.3-codex-xhigh", - name: "GPT-5.3 Codex XHigh", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "grok-4-1-fast-non-reasoning": { - id: "grok-4-1-fast-non-reasoning", - name: "Grok 4.1 Fast (Non-Reasoning)", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-11-17", - last_updated: "2025-11-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5 }, - limit: { context: 2000000, output: 16384 }, - }, - "gpt-5.1": { - id: "gpt-5.1", - name: "GPT-5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, output: 128000 }, - }, - "gemini-3.1-flash-lite-preview": { - id: "gemini-3.1-flash-lite-preview", - name: "Gemini 3.1 Flash Lite Preview", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-03-01", - last_updated: "2026-03-01", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1.5, cache_read: 0.025, cache_write: 1 }, - limit: { context: 1048576, output: 65536 }, - }, - o3: { - id: "o3", - name: "o3", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8 }, - limit: { context: 200000, output: 100000 }, - }, - "gemini-3-flash-preview": { - id: "gemini-3-flash-preview", - name: "Gemini 3 Flash Preview", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 3 }, - limit: { context: 1048576, output: 65536 }, - }, - "claude-opus-4-20250514": { - id: "claude-opus-4-20250514", - name: "Claude Opus 4", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-05-14", - last_updated: "2025-05-14", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75 }, - limit: { context: 200000, output: 32000 }, - }, - "gpt-4.1-nano": { - id: "gpt-4.1-nano", - name: "GPT-4.1 Nano", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 1047576, output: 32768 }, - }, - "claude-sonnet-4-5-20250929": { - id: "claude-sonnet-4-5-20250929", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, output: 64000 }, - }, - "gpt-5.2": { - id: "gpt-5.2", - name: "GPT-5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 400000, output: 128000 }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3 }, - limit: { context: 262144, output: 32768 }, - }, - "gpt-4.1": { - id: "gpt-4.1", - name: "GPT-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8 }, - limit: { context: 1047576, output: 32768 }, - }, - "o3-pro": { - id: "o3-pro", - name: "o3-pro", - family: "o-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-06-10", - last_updated: "2025-06-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 20, output: 40 }, - limit: { context: 200000, output: 100000 }, - }, - "gemini-3.1-pro-preview": { - id: "gemini-3.1-pro-preview", - name: "Gemini 3.1 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-19", - last_updated: "2026-02-19", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12 }, - limit: { context: 1048576, output: 65536 }, - }, - "claude-3-7-sonnet-20250219": { - id: "claude-3-7-sonnet-20250219", - name: "Claude Sonnet 3.7", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10-31", - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-haiku-4-5-20251001": { - id: "claude-haiku-4-5-20251001", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5 }, - limit: { context: 200000, output: 64000 }, - }, - "gpt-5": { - id: "gpt-5", - name: "GPT-5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, output: 128000 }, - }, - "o4-mini": { - id: "o4-mini", - name: "o4-mini", - family: "o-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4 }, - limit: { context: 200000, output: 100000 }, - }, - "gpt-4.1-mini": { - id: "gpt-4.1-mini", - name: "GPT-4.1 Mini", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.6 }, - limit: { context: 1047576, output: 32768 }, - }, - "llama-3.3-70b-versatile": { - id: "llama-3.3-70b-versatile", - name: "Llama 3.3 70B Versatile", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.59, output: 0.79 }, - limit: { context: 128000, output: 32768 }, - }, - "gpt-5.4": { - id: "gpt-5.4", - name: "GPT-5.4", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 15 }, - limit: { context: 1050000, input: 922000, output: 128000 }, - }, - "kimi-k2-turbo-preview": { - id: "kimi-k2-turbo-preview", - name: "Kimi K2 Turbo Preview", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-08", - last_updated: "2025-07-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 8 }, - limit: { context: 256000, output: 8192 }, - }, - "qwen-2.5-coder-32b": { - id: "qwen-2.5-coder-32b", - name: "Qwen 2.5 Coder 32B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-11-11", - last_updated: "2024-11-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.79, output: 0.79 }, - limit: { context: 128000, output: 8192 }, - }, - "gpt-5.1-codex": { - id: "gpt-5.1-codex", - name: "GPT-5.1 Codex", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "route-llm": { - id: "route-llm", - name: "Route LLM", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-01-01", - last_updated: "2024-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 128000, output: 16384 }, - }, - "gpt-5.3-chat-latest": { - id: "gpt-5.3-chat-latest", - name: "GPT-5.3 Chat Latest", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-01", - last_updated: "2026-03-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 400000, output: 128000 }, - }, - "o3-mini": { - id: "o3-mini", - name: "o3-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05", - release_date: "2024-12-20", - last_updated: "2025-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4 }, - limit: { context: 200000, output: 100000 }, - }, - "qwen3-max": { - id: "qwen3-max", - name: "Qwen3 Max", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.2, output: 6 }, - limit: { context: 131072, output: 16384 }, - }, - "grok-4-fast-non-reasoning": { - id: "grok-4-fast-non-reasoning", - name: "Grok 4 Fast (Non-Reasoning)", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5 }, - limit: { context: 2000000, output: 16384 }, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - name: "GPT-5 Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2 }, - limit: { context: 400000, output: 128000 }, - }, - "claude-sonnet-4-20250514": { - id: "claude-sonnet-4-20250514", - name: "Claude Sonnet 4", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-05-14", - last_updated: "2025-05-14", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, output: 64000 }, - }, - "gpt-5.1-chat-latest": { - id: "gpt-5.1-chat-latest", - name: "GPT-5.1 Chat Latest", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, output: 128000 }, - }, - "claude-opus-4-1-20250805": { - id: "claude-opus-4-1-20250805", - name: "Claude Opus 4.1", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75 }, - limit: { context: 200000, output: 32000 }, - }, - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-25", - last_updated: "2025-03-25", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 1048576, output: 65536 }, - }, - "gpt-5-nano": { - id: "gpt-5-nano", - name: "GPT-5 Nano", - family: "gpt-nano", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4 }, - limit: { context: 400000, output: 128000 }, - }, - "zai-org/glm-5": { - id: "zai-org/glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2 }, - limit: { context: 204800, output: 131072 }, - }, - "zai-org/glm-4.5": { - id: "zai-org/glm-4.5", - name: "GLM-4.5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 128000, output: 8192 }, - }, - "zai-org/glm-4.6": { - id: "zai-org/glm-4.6", - name: "GLM-4.6", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-03-01", - last_updated: "2025-03-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 128000, output: 8192 }, - }, - "zai-org/glm-4.7": { - id: "zai-org/glm-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-06-01", - last_updated: "2025-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 128000, output: 8192 }, - }, - "deepseek-ai/DeepSeek-R1": { - id: "deepseek-ai/DeepSeek-R1", - name: "DeepSeek R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 3, output: 7 }, - limit: { context: 128000, output: 8192 }, - }, - "deepseek-ai/DeepSeek-V3.2": { - id: "deepseek-ai/DeepSeek-V3.2", - name: "DeepSeek V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-06-15", - last_updated: "2025-06-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 0.4 }, - limit: { context: 128000, output: 8192 }, - }, - "deepseek-ai/DeepSeek-V3.1-Terminus": { - id: "deepseek-ai/DeepSeek-V3.1-Terminus", - name: "DeepSeek V3.1 Terminus", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-06-01", - last_updated: "2025-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 1 }, - limit: { context: 128000, output: 8192 }, - }, - "deepseek/deepseek-v3.1": { - id: "deepseek/deepseek-v3.1", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 1.66 }, - limit: { context: 128000, output: 8192 }, - }, - "meta-llama/Meta-Llama-3.1-8B-Instruct": { - id: "meta-llama/Meta-Llama-3.1-8B-Instruct", - name: "Llama 3.1 8B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.05 }, - limit: { context: 128000, output: 4096 }, - }, - "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo": { - id: "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo", - name: "Llama 3.1 405B Instruct Turbo", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 3.5, output: 3.5 }, - limit: { context: 128000, output: 4096 }, - }, - "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": { - id: "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", - name: "Llama 4 Maverick 17B 128E Instruct FP8", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.14, output: 0.59 }, - limit: { context: 1000000, output: 32768 }, - }, - "Qwen/QwQ-32B": { - id: "Qwen/QwQ-32B", - name: "QwQ 32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2024-11-28", - last_updated: "2024-11-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 0.4 }, - limit: { context: 32768, output: 32768 }, - }, - "Qwen/qwen3-coder-480b-a35b-instruct": { - id: "Qwen/qwen3-coder-480b-a35b-instruct", - name: "Qwen3 Coder 480B A35B Instruct", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-22", - last_updated: "2025-07-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.29, output: 1.2 }, - limit: { context: 262144, output: 65536 }, - }, - "Qwen/Qwen3-32B": { - id: "Qwen/Qwen3-32B", - name: "Qwen3 32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-04-29", - last_updated: "2025-04-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.29 }, - limit: { context: 128000, output: 8192 }, - }, - "Qwen/Qwen2.5-72B-Instruct": { - id: "Qwen/Qwen2.5-72B-Instruct", - name: "Qwen 2.5 72B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-09-19", - last_updated: "2024-09-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.11, output: 0.38 }, - limit: { context: 128000, output: 8192 }, - }, - "Qwen/Qwen3-235B-A22B-Instruct-2507": { - id: "Qwen/Qwen3-235B-A22B-Instruct-2507", - name: "Qwen3 235B A22B Instruct", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-01", - last_updated: "2025-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.6 }, - limit: { context: 262144, output: 8192 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT-OSS 120B", - family: "gpt-oss", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.08, output: 0.44 }, - limit: { context: 128000, output: 32768 }, - }, - }, - }, - "fireworks-ai": { - id: "fireworks-ai", - env: ["FIREWORKS_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.fireworks.ai/inference/v1/", - name: "Fireworks AI", - doc: "https://fireworks.ai/docs/", - models: { - "accounts/fireworks/models/kimi-k2-instruct": { - id: "accounts/fireworks/models/kimi-k2-instruct", - name: "Kimi K2 Instruct", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-11", - last_updated: "2025-07-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3 }, - limit: { context: 128000, output: 16384 }, - }, - "accounts/fireworks/models/glm-4p7": { - id: "accounts/fireworks/models/glm-4p7", - name: "GLM 4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.3 }, - limit: { context: 198000, output: 198000 }, - }, - "accounts/fireworks/models/glm-5": { - id: "accounts/fireworks/models/glm-5", - name: "GLM 5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2, cache_read: 0.5 }, - limit: { context: 202752, output: 131072 }, - }, - "accounts/fireworks/models/deepseek-v3p1": { - id: "accounts/fireworks/models/deepseek-v3p1", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.56, output: 1.68 }, - limit: { context: 163840, output: 163840 }, - }, - "accounts/fireworks/models/minimax-m2p1": { - id: "accounts/fireworks/models/minimax-m2p1", - name: "MiniMax-M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.03 }, - limit: { context: 200000, output: 200000 }, - }, - "accounts/fireworks/models/glm-4p5-air": { - id: "accounts/fireworks/models/glm-4p5-air", - name: "GLM 4.5 Air", - family: "glm-air", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-08-01", - last_updated: "2025-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.22, output: 0.88 }, - limit: { context: 131072, output: 131072 }, - }, - "accounts/fireworks/models/deepseek-v3p2": { - id: "accounts/fireworks/models/deepseek-v3p2", - name: "DeepSeek V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-09", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.56, output: 1.68, cache_read: 0.28 }, - limit: { context: 160000, output: 160000 }, - }, - "accounts/fireworks/models/minimax-m2p5": { - id: "accounts/fireworks/models/minimax-m2p5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.03 }, - limit: { context: 196608, output: 196608 }, - }, - "accounts/fireworks/models/gpt-oss-120b": { - id: "accounts/fireworks/models/gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 131072, output: 32768 }, - }, - "accounts/fireworks/models/kimi-k2p5": { - id: "accounts/fireworks/models/kimi-k2p5", - name: "Kimi K2.5", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3, cache_read: 0.1 }, - limit: { context: 256000, output: 256000 }, - }, - "accounts/fireworks/models/kimi-k2-thinking": { - id: "accounts/fireworks/models/kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5, cache_read: 0.3 }, - limit: { context: 256000, output: 256000 }, - }, - "accounts/fireworks/models/glm-4p5": { - id: "accounts/fireworks/models/glm-4p5", - name: "GLM 4.5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-29", - last_updated: "2025-07-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.19 }, - limit: { context: 131072, output: 131072 }, - }, - "accounts/fireworks/models/gpt-oss-20b": { - id: "accounts/fireworks/models/gpt-oss-20b", - name: "GPT OSS 20B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.2 }, - limit: { context: 131072, output: 32768 }, - }, - }, - }, - stepfun: { - id: "stepfun", - env: ["STEPFUN_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.stepfun.com/v1", - name: "StepFun", - doc: "https://platform.stepfun.com/docs/zh/overview/concept", - models: { - "step-3.5-flash": { - id: "step-3.5-flash", - name: "Step 3.5 Flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01-29", - last_updated: "2026-02-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.096, output: 0.288, cache_read: 0.019 }, - limit: { context: 256000, input: 256000, output: 256000 }, - }, - "step-2-16k": { - id: "step-2-16k", - name: "Step 2 (16K)", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2025-01-01", - last_updated: "2026-02-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 5.21, output: 16.44, cache_read: 1.04 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "step-1-32k": { - id: "step-1-32k", - name: "Step 1 (32K)", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2025-01-01", - last_updated: "2026-02-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.05, output: 9.59, cache_read: 0.41 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - }, - }, - gitlab: { - id: "gitlab", - env: ["GITLAB_TOKEN"], - npm: "@gitlab/gitlab-ai-provider", - name: "GitLab Duo", - doc: "https://docs.gitlab.com/user/duo_agent_platform/", - models: { - "duo-chat-gpt-5-2-codex": { - id: "duo-chat-gpt-5-2-codex", - name: "Agentic Chat (GPT-5.2 Codex)", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-01-22", - last_updated: "2026-01-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "duo-chat-opus-4-6": { - id: "duo-chat-opus-4-6", - name: "Agentic Chat (Claude Opus 4.6)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 1000000, output: 64000 }, - }, - "duo-chat-gpt-5-mini": { - id: "duo-chat-gpt-5-mini", - name: "Agentic Chat (GPT-5 Mini)", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2026-01-22", - last_updated: "2026-01-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "duo-chat-sonnet-4-5": { - id: "duo-chat-sonnet-4-5", - name: "Agentic Chat (Claude Sonnet 4.5)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2026-01-08", - last_updated: "2026-01-08", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 200000, output: 64000 }, - }, - "duo-chat-haiku-4-5": { - id: "duo-chat-haiku-4-5", - name: "Agentic Chat (Claude Haiku 4.5)", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2026-01-08", - last_updated: "2026-01-08", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 200000, output: 64000 }, - }, - "duo-chat-gpt-5-codex": { - id: "duo-chat-gpt-5-codex", - name: "Agentic Chat (GPT-5 Codex)", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2026-01-22", - last_updated: "2026-01-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "duo-chat-gpt-5-2": { - id: "duo-chat-gpt-5-2", - name: "Agentic Chat (GPT-5.2)", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-01-23", - last_updated: "2026-01-23", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "duo-chat-sonnet-4-6": { - id: "duo-chat-sonnet-4-6", - name: "Agentic Chat (Claude Sonnet 4.6)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08-31", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 1000000, output: 64000 }, - }, - "duo-chat-opus-4-5": { - id: "duo-chat-opus-4-5", - name: "Agentic Chat (Claude Opus 4.5)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2026-01-08", - last_updated: "2026-01-08", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 200000, output: 64000 }, - }, - "duo-chat-gpt-5-1": { - id: "duo-chat-gpt-5-1", - name: "Agentic Chat (GPT-5.1)", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2026-01-22", - last_updated: "2026-01-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - }, - }, - siliconflow: { - id: "siliconflow", - env: ["SILICONFLOW_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.siliconflow.com/v1", - name: "SiliconFlow", - doc: "https://cloud.siliconflow.com/models", - models: { - "nex-agi/DeepSeek-V3.1-Nex-N1": { - id: "nex-agi/DeepSeek-V3.1-Nex-N1", - name: "nex-agi/DeepSeek-V3.1-Nex-N1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 2 }, - limit: { context: 131000, output: 131000 }, - }, - "zai-org/GLM-4.5-Air": { - id: "zai-org/GLM-4.5-Air", - name: "zai-org/GLM-4.5-Air", - family: "glm-air", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-28", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.86 }, - limit: { context: 131000, output: 131000 }, - }, - "zai-org/GLM-4.6": { - id: "zai-org/GLM-4.6", - name: "zai-org/GLM-4.6", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-04", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.9 }, - limit: { context: 205000, output: 205000 }, - }, - "zai-org/GLM-4.7": { - id: "zai-org/GLM-4.7", - name: "zai-org/GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 205000, output: 205000 }, - }, - "zai-org/GLM-4.5V": { - id: "zai-org/GLM-4.5V", - name: "zai-org/GLM-4.5V", - family: "glm", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-13", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.86 }, - limit: { context: 66000, output: 66000 }, - }, - "zai-org/GLM-4.6V": { - id: "zai-org/GLM-4.6V", - name: "zai-org/GLM-4.6V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-07", - last_updated: "2025-12-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 131000, output: 131000 }, - }, - "zai-org/GLM-4.5": { - id: "zai-org/GLM-4.5", - name: "zai-org/GLM-4.5", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-28", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 131000, output: 131000 }, - }, - "zai-org/GLM-5": { - id: "zai-org/GLM-5", - name: "zai-org/GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2 }, - limit: { context: 205000, output: 205000 }, - }, - "MiniMaxAI/MiniMax-M2.1": { - id: "MiniMaxAI/MiniMax-M2.1", - name: "MiniMaxAI/MiniMax-M2.1", - family: "minimax", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 197000, output: 131000 }, - }, - "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B": { - id: "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", - name: "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-01-20", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0.18 }, - limit: { context: 131000, output: 131000 }, - }, - "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B": { - id: "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", - name: "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-01-20", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 131000, output: 131000 }, - }, - "deepseek-ai/DeepSeek-V3.2-Exp": { - id: "deepseek-ai/DeepSeek-V3.2-Exp", - name: "deepseek-ai/DeepSeek-V3.2-Exp", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-10", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.41 }, - limit: { context: 164000, output: 164000 }, - }, - "deepseek-ai/DeepSeek-R1": { - id: "deepseek-ai/DeepSeek-R1", - name: "deepseek-ai/DeepSeek-R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-05-28", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 2.18 }, - limit: { context: 164000, output: 164000 }, - }, - "deepseek-ai/deepseek-vl2": { - id: "deepseek-ai/deepseek-vl2", - name: "deepseek-ai/deepseek-vl2", - family: "deepseek", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-12-13", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 4000, output: 4000 }, - }, - "deepseek-ai/DeepSeek-V3.1": { - id: "deepseek-ai/DeepSeek-V3.1", - name: "deepseek-ai/DeepSeek-V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-25", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 1 }, - limit: { context: 164000, output: 164000 }, - }, - "deepseek-ai/DeepSeek-V3.2": { - id: "deepseek-ai/DeepSeek-V3.2", - name: "deepseek-ai/DeepSeek-V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-03", - last_updated: "2025-12-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.42 }, - limit: { context: 164000, output: 164000 }, - }, - "deepseek-ai/DeepSeek-V3": { - id: "deepseek-ai/DeepSeek-V3", - name: "deepseek-ai/DeepSeek-V3", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-12-26", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1 }, - limit: { context: 164000, output: 164000 }, - }, - "deepseek-ai/DeepSeek-V3.1-Terminus": { - id: "deepseek-ai/DeepSeek-V3.1-Terminus", - name: "deepseek-ai/DeepSeek-V3.1-Terminus", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-29", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 1 }, - limit: { context: 164000, output: 164000 }, - }, - "ByteDance-Seed/Seed-OSS-36B-Instruct": { - id: "ByteDance-Seed/Seed-OSS-36B-Instruct", - name: "ByteDance-Seed/Seed-OSS-36B-Instruct", - family: "seed", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-04", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.21, output: 0.57 }, - limit: { context: 262000, output: 262000 }, - }, - "tencent/Hunyuan-A13B-Instruct": { - id: "tencent/Hunyuan-A13B-Instruct", - name: "tencent/Hunyuan-A13B-Instruct", - family: "hunyuan", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-06-30", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.57 }, - limit: { context: 131000, output: 131000 }, - }, - "tencent/Hunyuan-MT-7B": { - id: "tencent/Hunyuan-MT-7B", - name: "tencent/Hunyuan-MT-7B", - family: "hunyuan", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 33000, output: 33000 }, - }, - "moonshotai/Kimi-K2-Instruct": { - id: "moonshotai/Kimi-K2-Instruct", - name: "moonshotai/Kimi-K2-Instruct", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-13", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.58, output: 2.29 }, - limit: { context: 131000, output: 131000 }, - }, - "moonshotai/Kimi-K2-Instruct-0905": { - id: "moonshotai/Kimi-K2-Instruct-0905", - name: "moonshotai/Kimi-K2-Instruct-0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-08", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 262000, output: 262000 }, - }, - "moonshotai/Kimi-K2.5": { - id: "moonshotai/Kimi-K2.5", - name: "moonshotai/Kimi-K2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.55, output: 3 }, - limit: { context: 262000, output: 262000 }, - }, - "moonshotai/Kimi-K2-Thinking": { - id: "moonshotai/Kimi-K2-Thinking", - name: "moonshotai/Kimi-K2-Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-11-07", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.55, output: 2.5 }, - limit: { context: 262000, output: 262000 }, - }, - "inclusionAI/Ling-flash-2.0": { - id: "inclusionAI/Ling-flash-2.0", - name: "inclusionAI/Ling-flash-2.0", - family: "ling", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.57 }, - limit: { context: 131000, output: 131000 }, - }, - "inclusionAI/Ring-flash-2.0": { - id: "inclusionAI/Ring-flash-2.0", - name: "inclusionAI/Ring-flash-2.0", - family: "ring", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-29", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.57 }, - limit: { context: 131000, output: 131000 }, - }, - "inclusionAI/Ling-mini-2.0": { - id: "inclusionAI/Ling-mini-2.0", - name: "inclusionAI/Ling-mini-2.0", - family: "ling", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-10", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.28 }, - limit: { context: 131000, output: 131000 }, - }, - "baidu/ERNIE-4.5-300B-A47B": { - id: "baidu/ERNIE-4.5-300B-A47B", - name: "baidu/ERNIE-4.5-300B-A47B", - family: "ernie", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-02", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.28, output: 1.1 }, - limit: { context: 131000, output: 131000 }, - }, - "stepfun-ai/Step-3.5-Flash": { - id: "stepfun-ai/Step-3.5-Flash", - name: "stepfun-ai/Step-3.5-Flash", - family: "step", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 262000, output: 262000 }, - }, - "meta-llama/Meta-Llama-3.1-8B-Instruct": { - id: "meta-llama/Meta-Llama-3.1-8B-Instruct", - name: "meta-llama/Meta-Llama-3.1-8B-Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-23", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.06, output: 0.06 }, - limit: { context: 33000, output: 4000 }, - }, - "Qwen/Qwen3-VL-30B-A3B-Thinking": { - id: "Qwen/Qwen3-VL-30B-A3B-Thinking", - name: "Qwen/Qwen3-VL-30B-A3B-Thinking", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-11", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 1 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-30B-A3B-Instruct-2507": { - id: "Qwen/Qwen3-30B-A3B-Instruct-2507", - name: "Qwen/Qwen3-30B-A3B-Instruct-2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-30", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09, output: 0.3 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-VL-235B-A22B-Instruct": { - id: "Qwen/Qwen3-VL-235B-A22B-Instruct", - name: "Qwen/Qwen3-VL-235B-A22B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-04", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.5 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-VL-32B-Instruct": { - id: "Qwen/Qwen3-VL-32B-Instruct", - name: "Qwen/Qwen3-VL-32B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-21", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/QwQ-32B": { - id: "Qwen/QwQ-32B", - name: "Qwen/QwQ-32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-03-06", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.58 }, - limit: { context: 131000, output: 131000 }, - }, - "Qwen/Qwen3-32B": { - id: "Qwen/Qwen3-32B", - name: "Qwen/Qwen3-32B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-30", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.57 }, - limit: { context: 131000, output: 131000 }, - }, - "Qwen/Qwen3-VL-235B-A22B-Thinking": { - id: "Qwen/Qwen3-VL-235B-A22B-Thinking", - name: "Qwen/Qwen3-VL-235B-A22B-Thinking", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-04", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.45, output: 3.5 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-Next-80B-A3B-Instruct": { - id: "Qwen/Qwen3-Next-80B-A3B-Instruct", - name: "Qwen/Qwen3-Next-80B-A3B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 1.4 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-235B-A22B-Thinking-2507": { - id: "Qwen/Qwen3-235B-A22B-Thinking-2507", - name: "Qwen/Qwen3-235B-A22B-Thinking-2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-28", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.13, output: 0.6 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-Omni-30B-A3B-Instruct": { - id: "Qwen/Qwen3-Omni-30B-A3B-Instruct", - name: "Qwen/Qwen3-Omni-30B-A3B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-04", - last_updated: "2025-11-25", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 66000, output: 66000 }, - }, - "Qwen/Qwen2.5-VL-7B-Instruct": { - id: "Qwen/Qwen2.5-VL-7B-Instruct", - name: "Qwen/Qwen2.5-VL-7B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-01-28", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.05 }, - limit: { context: 33000, output: 4000 }, - }, - "Qwen/Qwen3-30B-A3B-Thinking-2507": { - id: "Qwen/Qwen3-30B-A3B-Thinking-2507", - name: "Qwen/Qwen3-30B-A3B-Thinking-2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-31", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09, output: 0.3 }, - limit: { context: 262000, output: 131000 }, - }, - "Qwen/Qwen2.5-32B-Instruct": { - id: "Qwen/Qwen2.5-32B-Instruct", - name: "Qwen/Qwen2.5-32B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-09-19", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0.18 }, - limit: { context: 33000, output: 4000 }, - }, - "Qwen/Qwen2.5-Coder-32B-Instruct": { - id: "Qwen/Qwen2.5-Coder-32B-Instruct", - name: "Qwen/Qwen2.5-Coder-32B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-11-11", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0.18 }, - limit: { context: 33000, output: 4000 }, - }, - "Qwen/Qwen3-8B": { - id: "Qwen/Qwen3-8B", - name: "Qwen/Qwen3-8B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-30", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.06, output: 0.06 }, - limit: { context: 131000, output: 131000 }, - }, - "Qwen/Qwen3-Coder-480B-A35B-Instruct": { - id: "Qwen/Qwen3-Coder-480B-A35B-Instruct", - name: "Qwen/Qwen3-Coder-480B-A35B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-31", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-Omni-30B-A3B-Thinking": { - id: "Qwen/Qwen3-Omni-30B-A3B-Thinking", - name: "Qwen/Qwen3-Omni-30B-A3B-Thinking", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-04", - last_updated: "2025-11-25", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 66000, output: 66000 }, - }, - "Qwen/Qwen2.5-7B-Instruct": { - id: "Qwen/Qwen2.5-7B-Instruct", - name: "Qwen/Qwen2.5-7B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.05 }, - limit: { context: 33000, output: 4000 }, - }, - "Qwen/Qwen2.5-14B-Instruct": { - id: "Qwen/Qwen2.5-14B-Instruct", - name: "Qwen/Qwen2.5-14B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 33000, output: 4000 }, - }, - "Qwen/Qwen2.5-VL-72B-Instruct": { - id: "Qwen/Qwen2.5-VL-72B-Instruct", - name: "Qwen/Qwen2.5-VL-72B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-01-28", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.59, output: 0.59 }, - limit: { context: 131000, output: 4000 }, - }, - "Qwen/Qwen2.5-72B-Instruct": { - id: "Qwen/Qwen2.5-72B-Instruct", - name: "Qwen/Qwen2.5-72B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.59, output: 0.59 }, - limit: { context: 33000, output: 4000 }, - }, - "Qwen/Qwen2.5-72B-Instruct-128K": { - id: "Qwen/Qwen2.5-72B-Instruct-128K", - name: "Qwen/Qwen2.5-72B-Instruct-128K", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.59, output: 0.59 }, - limit: { context: 131000, output: 4000 }, - }, - "Qwen/Qwen3-235B-A22B": { - id: "Qwen/Qwen3-235B-A22B", - name: "Qwen/Qwen3-235B-A22B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-30", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.35, output: 1.42 }, - limit: { context: 131000, output: 131000 }, - }, - "Qwen/Qwen3-VL-8B-Instruct": { - id: "Qwen/Qwen3-VL-8B-Instruct", - name: "Qwen/Qwen3-VL-8B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-15", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0.68 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-Next-80B-A3B-Thinking": { - id: "Qwen/Qwen3-Next-80B-A3B-Thinking", - name: "Qwen/Qwen3-Next-80B-A3B-Thinking", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-25", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.57 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-Omni-30B-A3B-Captioner": { - id: "Qwen/Qwen3-Omni-30B-A3B-Captioner", - name: "Qwen/Qwen3-Omni-30B-A3B-Captioner", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-04", - last_updated: "2025-11-25", - modalities: { input: ["audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 66000, output: 66000 }, - }, - "Qwen/Qwen3-VL-30B-A3B-Instruct": { - id: "Qwen/Qwen3-VL-30B-A3B-Instruct", - name: "Qwen/Qwen3-VL-30B-A3B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-05", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 1 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-VL-8B-Thinking": { - id: "Qwen/Qwen3-VL-8B-Thinking", - name: "Qwen/Qwen3-VL-8B-Thinking", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-15", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 2 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-Coder-30B-A3B-Instruct": { - id: "Qwen/Qwen3-Coder-30B-A3B-Instruct", - name: "Qwen/Qwen3-Coder-30B-A3B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-01", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.28 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-VL-32B-Thinking": { - id: "Qwen/Qwen3-VL-32B-Thinking", - name: "Qwen/Qwen3-VL-32B-Thinking", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-21", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.5 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-235B-A22B-Instruct-2507": { - id: "Qwen/Qwen3-235B-A22B-Instruct-2507", - name: "Qwen/Qwen3-235B-A22B-Instruct-2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-23", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09, output: 0.6 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-14B": { - id: "Qwen/Qwen3-14B", - name: "Qwen/Qwen3-14B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-30", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.28 }, - limit: { context: 131000, output: 131000 }, - }, - "Qwen/Qwen2.5-VL-32B-Instruct": { - id: "Qwen/Qwen2.5-VL-32B-Instruct", - name: "Qwen/Qwen2.5-VL-32B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-03-24", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.27 }, - limit: { context: 131000, output: 131000 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "openai/gpt-oss-120b", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-13", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.45 }, - limit: { context: 131000, output: 8000 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "openai/gpt-oss-20b", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-13", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.04, output: 0.18 }, - limit: { context: 131000, output: 8000 }, - }, - "THUDM/GLM-4-32B-0414": { - id: "THUDM/GLM-4-32B-0414", - name: "THUDM/GLM-4-32B-0414", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.27 }, - limit: { context: 33000, output: 33000 }, - }, - "THUDM/GLM-4-9B-0414": { - id: "THUDM/GLM-4-9B-0414", - name: "THUDM/GLM-4-9B-0414", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.086, output: 0.086 }, - limit: { context: 33000, output: 33000 }, - }, - "THUDM/GLM-Z1-32B-0414": { - id: "THUDM/GLM-Z1-32B-0414", - name: "THUDM/GLM-Z1-32B-0414", - family: "glm-z", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.57 }, - limit: { context: 131000, output: 131000 }, - }, - "THUDM/GLM-Z1-9B-0414": { - id: "THUDM/GLM-Z1-9B-0414", - name: "THUDM/GLM-Z1-9B-0414", - family: "glm-z", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.086, output: 0.086 }, - limit: { context: 131000, output: 131000 }, - }, - }, - }, - togetherai: { - id: "togetherai", - env: ["TOGETHER_API_KEY"], - npm: "@ai-sdk/togetherai", - name: "Together AI", - doc: "https://docs.together.ai/docs/serverless-models", - models: { - "zai-org/GLM-4.6": { - id: "zai-org/GLM-4.6", - name: "GLM 4.6", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 200000, output: 200000 }, - }, - "zai-org/GLM-4.7": { - id: "zai-org/GLM-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-25", - last_updated: "2025-07-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.45, output: 2 }, - limit: { context: 200000, output: 200000 }, - }, - "zai-org/GLM-5": { - id: "zai-org/GLM-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-11", - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2 }, - limit: { context: 202752, output: 131072 }, - }, - "essentialai/Rnj-1-Instruct": { - id: "essentialai/Rnj-1-Instruct", - name: "Rnj-1 Instruct", - family: "rnj", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-12-05", - last_updated: "2025-12-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 32768, output: 32768 }, - }, - "MiniMaxAI/MiniMax-M2.5": { - id: "MiniMaxAI/MiniMax-M2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.06 }, - limit: { context: 204800, output: 131072 }, - }, - "deepseek-ai/DeepSeek-V3-1": { - id: "deepseek-ai/DeepSeek-V3-1", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 1.7 }, - limit: { context: 131072, output: 131072 }, - }, - "deepseek-ai/DeepSeek-R1": { - id: "deepseek-ai/DeepSeek-R1", - name: "DeepSeek R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-07", - release_date: "2024-12-26", - last_updated: "2025-03-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 3, output: 7 }, - limit: { context: 163839, output: 163839 }, - }, - "deepseek-ai/DeepSeek-V3": { - id: "deepseek-ai/DeepSeek-V3", - name: "DeepSeek V3", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-01-20", - last_updated: "2025-05-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.25, output: 1.25 }, - limit: { context: 131072, output: 131072 }, - }, - "moonshotai/Kimi-K2-Instruct": { - id: "moonshotai/Kimi-K2-Instruct", - name: "Kimi K2 Instruct", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-14", - last_updated: "2025-07-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3 }, - limit: { context: 131072, output: 131072 }, - }, - "moonshotai/Kimi-K2.5": { - id: "moonshotai/Kimi-K2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2026-01", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 2.8 }, - limit: { context: 262144, output: 262144 }, - }, - "meta-llama/Llama-3.3-70B-Instruct-Turbo": { - id: "meta-llama/Llama-3.3-70B-Instruct-Turbo", - name: "Llama 3.3 70B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.88, output: 0.88 }, - limit: { context: 131072, output: 131072 }, - }, - "Qwen/Qwen3-Next-80B-A3B-Instruct": { - id: "Qwen/Qwen3-Next-80B-A3B-Instruct", - name: "Qwen3-Next-80B-A3B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-25", - last_updated: "2025-07-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 1.5 }, - limit: { context: 262144, output: 262144 }, - }, - "Qwen/Qwen3-235B-A22B-Instruct-2507-tput": { - id: "Qwen/Qwen3-235B-A22B-Instruct-2507-tput", - name: "Qwen3 235B A22B Instruct 2507 FP8", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-25", - last_updated: "2025-07-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 262144, output: 262144 }, - }, - "Qwen/Qwen3.5-397B-A17B": { - id: "Qwen/Qwen3.5-397B-A17B", - name: "Qwen3.5 397B A17B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-16", - last_updated: "2026-02-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3.6 }, - limit: { context: 262144, output: 130000 }, - }, - "Qwen/Qwen3-Coder-Next-FP8": { - id: "Qwen/Qwen3-Coder-Next-FP8", - name: "Qwen3 Coder Next FP8", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2026-02-03", - release_date: "2026-02-03", - last_updated: "2026-02-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 1.2 }, - limit: { context: 262144, output: 262144 }, - }, - "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8": { - id: "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8", - name: "Qwen3 Coder 480B A35B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 2 }, - limit: { context: 262144, output: 262144 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 131072, output: 131072 }, - }, - }, - }, - clarifai: { - id: "clarifai", - env: ["CLARIFAI_PAT"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.clarifai.com/v2/ext/openai/v1", - name: "Clarifai", - doc: "https://docs.clarifai.com/compute/inference/", - models: { - "minimaxai/chat-completion/models/MiniMax-M2_5-high-throughput": { - id: "minimaxai/chat-completion/models/MiniMax-M2_5-high-throughput", - name: "MiniMax-M2.5 High Throughput", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 204800, output: 131072 }, - }, - "arcee_ai/AFM/models/trinity-mini": { - id: "arcee_ai/AFM/models/trinity-mini", - name: "Trinity Mini", - family: "trinity-mini", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-12", - last_updated: "2026-02-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.045, output: 0.15 }, - limit: { context: 131072, output: 131072 }, - }, - "deepseek-ai/deepseek-ocr/models/DeepSeek-OCR": { - id: "deepseek-ai/deepseek-ocr/models/DeepSeek-OCR", - name: "DeepSeek OCR", - family: "deepseek", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-10-20", - last_updated: "2026-02-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.7 }, - limit: { context: 8192, output: 8192 }, - }, - "clarifai/main/models/mm-poly-8b": { - id: "clarifai/main/models/mm-poly-8b", - name: "MM Poly 8B", - family: "mm-poly", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-06", - last_updated: "2026-02-25", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.658, output: 1.11 }, - limit: { context: 32768, output: 4096 }, - }, - "qwen/qwenCoder/models/Qwen3-Coder-30B-A3B-Instruct": { - id: "qwen/qwenCoder/models/Qwen3-Coder-30B-A3B-Instruct", - name: "Qwen3 Coder 30B A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-31", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.11458, output: 0.74812 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen/qwenLM/models/Qwen3-30B-A3B-Instruct-2507": { - id: "qwen/qwenLM/models/Qwen3-30B-A3B-Instruct-2507", - name: "Qwen3 30B A3B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-30", - last_updated: "2026-02-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.5 }, - limit: { context: 262144, output: 262144 }, - }, - "qwen/qwenLM/models/Qwen3-30B-A3B-Thinking-2507": { - id: "qwen/qwenLM/models/Qwen3-30B-A3B-Thinking-2507", - name: "Qwen3 30B A3B Thinking 2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-31", - last_updated: "2026-02-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.36, output: 1.3 }, - limit: { context: 262144, output: 131072 }, - }, - "mistralai/completion/models/Ministral-3-14B-Reasoning-2512": { - id: "mistralai/completion/models/Ministral-3-14B-Reasoning-2512", - name: "Ministral 3 14B Reasoning 2512", - family: "ministral", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-12", - release_date: "2025-12-01", - last_updated: "2025-12-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 2.5, output: 1.7 }, - limit: { context: 262144, output: 262144 }, - }, - "mistralai/completion/models/Ministral-3-3B-Reasoning-2512": { - id: "mistralai/completion/models/Ministral-3-3B-Reasoning-2512", - name: "Ministral 3 3B Reasoning 2512", - family: "ministral", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12", - last_updated: "2026-02-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 1.039, output: 0.54825 }, - limit: { context: 262144, output: 262144 }, - }, - "openai/chat-completion/models/gpt-oss-120b-high-throughput": { - id: "openai/chat-completion/models/gpt-oss-120b-high-throughput", - name: "GPT OSS 120B High Throughput", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2026-02-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.36 }, - limit: { context: 131072, output: 16384 }, - }, - "openai/chat-completion/models/gpt-oss-20b": { - id: "openai/chat-completion/models/gpt-oss-20b", - name: "GPT OSS 20B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-12-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.045, output: 0.18 }, - limit: { context: 131072, output: 16384 }, - }, - }, - }, - berget: { - id: "berget", - env: ["BERGET_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.berget.ai/v1", - name: "Berget.AI", - doc: "https://api.berget.ai", - models: { - "zai-org/GLM-4.7": { - id: "zai-org/GLM-4.7", - name: "GLM 4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-12", - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 2.3 }, - limit: { context: 128000, output: 8192 }, - }, - "BAAI/bge-reranker-v2-m3": { - id: "BAAI/bge-reranker-v2-m3", - name: "bge-reranker-v2-m3", - family: "bge", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2025-04", - release_date: "2025-04-23", - last_updated: "2025-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 512, output: 512 }, - }, - "intfloat/multilingual-e5-large-instruct": { - id: "intfloat/multilingual-e5-large-instruct", - name: "Multilingual-E5-large-instruct", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2025-04", - release_date: "2025-04-27", - last_updated: "2025-04-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0 }, - limit: { context: 512, output: 1024 }, - }, - "intfloat/multilingual-e5-large": { - id: "intfloat/multilingual-e5-large", - name: "Multilingual-E5-large", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2025-09", - release_date: "2025-09-11", - last_updated: "2025-09-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0 }, - limit: { context: 512, output: 1024 }, - }, - "KBLab/kb-whisper-large": { - id: "KBLab/kb-whisper-large", - name: "KB-Whisper-Large", - family: "whisper", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2025-04", - release_date: "2025-04-27", - last_updated: "2025-04-27", - modalities: { input: ["audio"], output: ["text"] }, - open_weights: true, - cost: { input: 3, output: 3 }, - limit: { context: 480000, output: 4800 }, - }, - "meta-llama/Llama-3.3-70B-Instruct": { - id: "meta-llama/Llama-3.3-70B-Instruct", - name: "Llama 3.3 70B Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-12", - release_date: "2025-04-27", - last_updated: "2025-04-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.9, output: 0.9 }, - limit: { context: 128000, output: 8192 }, - }, - "mistralai/Mistral-Small-3.2-24B-Instruct-2506": { - id: "mistralai/Mistral-Small-3.2-24B-Instruct-2506", - name: "Mistral Small 3.2 24B Instruct 2506", - family: "mistral-small", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-10-01", - last_updated: "2025-10-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.3 }, - limit: { context: 32000, output: 8192 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT-OSS-120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 128000, output: 8192 }, - }, - }, - }, - lucidquery: { - id: "lucidquery", - env: ["LUCIDQUERY_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://lucidquery.com/api/v1", - name: "LucidQuery AI", - doc: "https://lucidquery.com/api/docs", - models: { - "lucidquery-nexus-coder": { - id: "lucidquery-nexus-coder", - name: "LucidQuery Nexus Coder", - family: "lucid", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-08-01", - release_date: "2025-09-01", - last_updated: "2025-09-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 5 }, - limit: { context: 250000, output: 60000 }, - }, - "lucidnova-rf1-100b": { - id: "lucidnova-rf1-100b", - name: "LucidNova RF1 100B", - family: "nova", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-09-16", - release_date: "2024-12-28", - last_updated: "2025-09-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 5 }, - limit: { context: 120000, output: 8000 }, - }, - }, - }, - "zhipuai-coding-plan": { - id: "zhipuai-coding-plan", - env: ["ZHIPU_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://open.bigmodel.cn/api/coding/paas/v4", - name: "Zhipu AI Coding Plan", - doc: "https://docs.bigmodel.cn/cn/coding-plan/overview", - models: { - "glm-5": { - id: "glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "glm-4.6v-flash": { - id: "glm-4.6v-flash", - name: "GLM-4.6V-Flash", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - "glm-4.6v": { - id: "glm-4.6v", - name: "GLM-4.6V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - "glm-4.5v": { - id: "glm-4.5v", - name: "GLM-4.5V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-08-11", - last_updated: "2025-08-11", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 64000, output: 16384 }, - }, - "glm-4.7": { - id: "glm-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "glm-4.6": { - id: "glm-4.6", - name: "GLM-4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "glm-4.5-flash": { - id: "glm-4.5-flash", - name: "GLM-4.5-Flash", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 131072, output: 98304 }, - }, - "glm-4.5": { - id: "glm-4.5", - name: "GLM-4.5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 131072, output: 98304 }, - }, - "glm-4.5-air": { - id: "glm-4.5-air", - name: "GLM-4.5-Air", - family: "glm-air", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 131072, output: 98304 }, - }, - }, - }, - deepseek: { - id: "deepseek", - env: ["DEEPSEEK_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.deepseek.com", - name: "DeepSeek", - doc: "https://api-docs.deepseek.com/quick_start/pricing", - models: { - "deepseek-reasoner": { - id: "deepseek-reasoner", - name: "DeepSeek Reasoner", - family: "deepseek-thinking", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-09", - release_date: "2025-12-01", - last_updated: "2026-02-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 0.42, cache_read: 0.028 }, - limit: { context: 128000, output: 64000 }, - }, - "deepseek-chat": { - id: "deepseek-chat", - name: "DeepSeek Chat", - family: "deepseek", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-12-01", - last_updated: "2026-02-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 0.42, cache_read: 0.028 }, - limit: { context: 128000, output: 8192 }, - }, - }, - }, - lmstudio: { - id: "lmstudio", - env: ["LMSTUDIO_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "http://127.0.0.1:1234/v1", - name: "LMStudio", - doc: "https://lmstudio.ai/models", - models: { - "qwen/qwen3-30b-a3b-2507": { - id: "qwen/qwen3-30b-a3b-2507", - name: "Qwen3 30B A3B 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-30", - last_updated: "2025-07-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 16384 }, - }, - "qwen/qwen3-coder-30b": { - id: "qwen/qwen3-coder-30b", - name: "Qwen3 Coder 30B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 65536 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "GPT OSS 20B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 32768 }, - }, - }, - }, - openrouter: { - id: "openrouter", - env: ["OPENROUTER_API_KEY"], - npm: "@openrouter/ai-sdk-provider", - api: "https://openrouter.ai/api/v1", - name: "OpenRouter", - doc: "https://openrouter.ai/models", - models: { - "prime-intellect/intellect-3": { - id: "prime-intellect/intellect-3", - name: "Intellect 3", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-01-15", - last_updated: "2025-01-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 1.1 }, - limit: { context: 131072, output: 8192 }, - }, - "featherless/qwerky-72b": { - id: "featherless/qwerky-72b", - name: "Qwerky 72B", - family: "qwerky", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2025-03-20", - last_updated: "2025-03-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 8192 }, - }, - "allenai/molmo-2-8b:free": { - id: "allenai/molmo-2-8b:free", - name: "Molmo2 8B (free)", - family: "allenai", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2026-01-09", - last_updated: "2026-01-31", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 36864, output: 36864 }, - }, - "nvidia/nemotron-nano-9b-v2:free": { - id: "nvidia/nemotron-nano-9b-v2:free", - name: "Nemotron Nano 9B V2 (free)", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-09", - release_date: "2025-09-05", - last_updated: "2025-08-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 128000 }, - }, - "nvidia/nemotron-nano-12b-v2-vl:free": { - id: "nvidia/nemotron-nano-12b-v2-vl:free", - name: "Nemotron Nano 12B 2 VL (free)", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-10-28", - last_updated: "2026-01-31", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 128000 }, - }, - "nvidia/nemotron-3-nano-30b-a3b:free": { - id: "nvidia/nemotron-3-nano-30b-a3b:free", - name: "Nemotron 3 Nano 30B A3B (free)", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-12-14", - last_updated: "2026-01-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 256000 }, - }, - "nvidia/nemotron-nano-9b-v2": { - id: "nvidia/nemotron-nano-9b-v2", - name: "nvidia-nemotron-nano-9b-v2", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2025-08-18", - last_updated: "2025-08-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.16 }, - limit: { context: 131072, output: 131072 }, - }, - "arcee-ai/trinity-large-preview:free": { - id: "arcee-ai/trinity-large-preview:free", - name: "Trinity Large Preview", - family: "trinity", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-06", - release_date: "2026-01-28", - last_updated: "2026-01-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 131072 }, - }, - "arcee-ai/trinity-mini:free": { - id: "arcee-ai/trinity-mini:free", - name: "Trinity Mini", - family: "trinity-mini", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-06", - release_date: "2026-01-28", - last_updated: "2026-01-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 131072 }, - }, - "xiaomi/mimo-v2-flash": { - id: "xiaomi/mimo-v2-flash", - name: "MiMo-V2-Flash", - family: "mimo", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-12-14", - last_updated: "2025-12-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3, cache_read: 0.01 }, - limit: { context: 262144, output: 65536 }, - }, - "microsoft/mai-ds-r1:free": { - id: "microsoft/mai-ds-r1:free", - name: "MAI DS R1 (free)", - family: "mai", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-21", - last_updated: "2025-04-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 163840, output: 163840 }, - }, - "sarvamai/sarvam-m:free": { - id: "sarvamai/sarvam-m:free", - name: "Sarvam-M (free)", - family: "sarvam", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-25", - last_updated: "2025-05-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 32768 }, - }, - "liquid/lfm-2.5-1.2b-thinking:free": { - id: "liquid/lfm-2.5-1.2b-thinking:free", - name: "LFM2.5-1.2B-Thinking (free)", - family: "liquid", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2026-01-20", - last_updated: "2026-01-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 32768 }, - }, - "liquid/lfm-2.5-1.2b-instruct:free": { - id: "liquid/lfm-2.5-1.2b-instruct:free", - name: "LFM2.5-1.2B-Instruct (free)", - family: "liquid", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2026-01-20", - last_updated: "2026-01-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 32768 }, - }, - "inception/mercury-2": { - id: "inception/mercury-2", - name: "Mercury 2", - family: "mercury", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-03-04", - last_updated: "2026-03-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.75, cache_read: 0.025 }, - limit: { context: 128000, output: 50000 }, - }, - "inception/mercury": { - id: "inception/mercury", - name: "Mercury", - family: "mercury", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-06-26", - last_updated: "2025-06-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.75, cache_read: 0.025 }, - limit: { context: 128000, output: 32000 }, - }, - "inception/mercury-coder": { - id: "inception/mercury-coder", - name: "Mercury Coder", - family: "mercury", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-30", - last_updated: "2025-04-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.75, cache_read: 0.025 }, - limit: { context: 128000, output: 32000 }, - }, - "thudm/glm-z1-32b:free": { - id: "thudm/glm-z1-32b:free", - name: "GLM Z1 32B (free)", - family: "glm-z", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-17", - last_updated: "2025-04-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 32768 }, - }, - "sourceful/riverflow-v2-fast-preview": { - id: "sourceful/riverflow-v2-fast-preview", - name: "Riverflow V2 Fast Preview", - family: "sourceful", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2025-12-08", - last_updated: "2026-01-28", - modalities: { input: ["text", "image"], output: ["image"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 8192, output: 8192 }, - }, - "sourceful/riverflow-v2-max-preview": { - id: "sourceful/riverflow-v2-max-preview", - name: "Riverflow V2 Max Preview", - family: "sourceful", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2025-12-08", - last_updated: "2026-01-28", - modalities: { input: ["text", "image"], output: ["image"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 8192, output: 8192 }, - }, - "sourceful/riverflow-v2-standard-preview": { - id: "sourceful/riverflow-v2-standard-preview", - name: "Riverflow V2 Standard Preview", - family: "sourceful", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2025-12-08", - last_updated: "2026-01-28", - modalities: { input: ["text", "image"], output: ["image"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 8192, output: 8192 }, - }, - "rekaai/reka-flash-3": { - id: "rekaai/reka-flash-3", - name: "Reka Flash 3", - family: "reka", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-03-12", - last_updated: "2025-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 8192 }, - }, - "stepfun/step-3.5-flash:free": { - id: "stepfun/step-3.5-flash:free", - name: "Step 3.5 Flash (free)", - family: "step", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01-29", - last_updated: "2026-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 256000 }, - }, - "stepfun/step-3.5-flash": { - id: "stepfun/step-3.5-flash", - name: "Step 3.5 Flash", - family: "step", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01-29", - last_updated: "2026-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3, cache_read: 0.02 }, - limit: { context: 256000, output: 256000 }, - }, - "cognitivecomputations/dolphin3.0-r1-mistral-24b": { - id: "cognitivecomputations/dolphin3.0-r1-mistral-24b", - name: "Dolphin3.0 R1 Mistral 24B", - family: "mistral", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-02-13", - last_updated: "2025-02-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 8192 }, - }, - "cognitivecomputations/dolphin3.0-mistral-24b": { - id: "cognitivecomputations/dolphin3.0-mistral-24b", - name: "Dolphin3.0 Mistral 24B", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-02-13", - last_updated: "2025-02-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 8192 }, - }, - "cognitivecomputations/dolphin-mistral-24b-venice-edition:free": { - id: "cognitivecomputations/dolphin-mistral-24b-venice-edition:free", - name: "Uncensored (free)", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-07-09", - last_updated: "2026-01-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 32768 }, - }, - "kwaipilot/kat-coder-pro:free": { - id: "kwaipilot/kat-coder-pro:free", - name: "Kat Coder Pro (free)", - family: "kat-coder", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-10", - last_updated: "2025-11-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 65536 }, - }, - "deepseek/deepseek-v3.1-terminus:exacto": { - id: "deepseek/deepseek-v3.1-terminus:exacto", - name: "DeepSeek V3.1 Terminus (exacto)", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-09-22", - last_updated: "2025-09-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 1 }, - limit: { context: 131072, output: 65536 }, - }, - "deepseek/deepseek-r1-0528:free": { - id: "deepseek/deepseek-r1-0528:free", - name: "R1 0528 (free)", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 163840, output: 163840 }, - }, - "deepseek/deepseek-r1-distill-qwen-14b": { - id: "deepseek/deepseek-r1-distill-qwen-14b", - name: "DeepSeek R1 Distill Qwen 14B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2025-01-29", - last_updated: "2025-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 64000, output: 8192 }, - }, - "deepseek/deepseek-r1:free": { - id: "deepseek/deepseek-r1:free", - name: "R1 (free)", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 163840, output: 163840 }, - }, - "deepseek/deepseek-r1-0528-qwen3-8b:free": { - id: "deepseek/deepseek-r1-0528-qwen3-8b:free", - name: "Deepseek R1 0528 Qwen3 8B (free)", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-29", - last_updated: "2025-05-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 131072 }, - }, - "deepseek/deepseek-v3.2-speciale": { - id: "deepseek/deepseek-v3.2-speciale", - name: "DeepSeek V3.2 Speciale", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 0.41 }, - limit: { context: 163840, output: 65536 }, - }, - "deepseek/deepseek-chat-v3.1": { - id: "deepseek/deepseek-chat-v3.1", - name: "DeepSeek-V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 163840, output: 163840 }, - }, - "deepseek/deepseek-chat-v3-0324": { - id: "deepseek/deepseek-chat-v3-0324", - name: "DeepSeek V3 0324", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-03-24", - last_updated: "2025-03-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 16384, output: 8192 }, - }, - "deepseek/deepseek-r1-distill-llama-70b": { - id: "deepseek/deepseek-r1-distill-llama-70b", - name: "DeepSeek R1 Distill Llama 70B", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-01-23", - last_updated: "2025-01-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 8192, output: 8192 }, - }, - "deepseek/deepseek-v3.1-terminus": { - id: "deepseek/deepseek-v3.1-terminus", - name: "DeepSeek V3.1 Terminus", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-09-22", - last_updated: "2025-09-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 1 }, - limit: { context: 131072, output: 65536 }, - }, - "deepseek/deepseek-v3.2": { - id: "deepseek/deepseek-v3.2", - name: "DeepSeek V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 0.4 }, - limit: { context: 163840, output: 65536 }, - }, - "deepseek/deepseek-v3-base:free": { - id: "deepseek/deepseek-v3-base:free", - name: "DeepSeek V3 Base (free)", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-03", - release_date: "2025-03-29", - last_updated: "2025-03-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 163840, output: 163840 }, - }, - "openrouter/hunter-alpha": { - id: "openrouter/hunter-alpha", - name: "Hunter Alpha", - family: "alpha", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - structured_output: true, - temperature: true, - knowledge: "2026-03-11", - release_date: "2026-03-11", - last_updated: "2026-03-11", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 1048576, output: 64000 }, - }, - "openrouter/sherlock-think-alpha": { - id: "openrouter/sherlock-think-alpha", - name: "Sherlock Think Alpha", - family: "sherlock", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-15", - last_updated: "2025-12-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 1840000, output: 0 }, - }, - "openrouter/sherlock-dash-alpha": { - id: "openrouter/sherlock-dash-alpha", - name: "Sherlock Dash Alpha", - family: "sherlock", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-15", - last_updated: "2025-12-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 1840000, output: 0 }, - }, - "openrouter/free": { - id: "openrouter/free", - name: "Free Models Router", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-01", - last_updated: "2026-02-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 200000, input: 200000, output: 8000 }, - }, - "openrouter/healer-alpha": { - id: "openrouter/healer-alpha", - name: "Healer Alpha", - family: "alpha", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - structured_output: true, - temperature: true, - knowledge: "2026-03-11", - release_date: "2026-03-11", - last_updated: "2026-03-11", - modalities: { input: ["text", "image", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 64000 }, - }, - "openrouter/aurora-alpha": { - id: "openrouter/aurora-alpha", - name: "Aurora Alpha", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-09", - last_updated: "2026-02-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 50000 }, - }, - "moonshotai/kimi-dev-72b:free": { - id: "moonshotai/kimi-dev-72b:free", - name: "Kimi Dev 72b (free)", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-06-16", - last_updated: "2025-06-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 131072 }, - }, - "moonshotai/kimi-k2": { - id: "moonshotai/kimi-k2", - name: "Kimi K2", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-11", - last_updated: "2025-07-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.2 }, - limit: { context: 131072, output: 32768 }, - }, - "moonshotai/kimi-k2-0905": { - id: "moonshotai/kimi-k2-0905", - name: "Kimi K2 Instruct 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5 }, - limit: { context: 262144, output: 16384 }, - }, - "moonshotai/kimi-k2-0905:exacto": { - id: "moonshotai/kimi-k2-0905:exacto", - name: "Kimi K2 Instruct 0905 (exacto)", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5 }, - limit: { context: 262144, output: 16384 }, - }, - "moonshotai/kimi-k2.5": { - id: "moonshotai/kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3, cache_read: 0.1 }, - limit: { context: 262144, output: 262144 }, - }, - "moonshotai/kimi-k2-thinking": { - id: "moonshotai/kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - structured_output: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5, cache_read: 0.15 }, - limit: { context: 262144, output: 262144 }, - }, - "moonshotai/kimi-k2:free": { - id: "moonshotai/kimi-k2:free", - name: "Kimi K2 (free)", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-11", - last_updated: "2025-07-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32800, output: 32800 }, - }, - "google/gemini-2.5-flash-lite-preview-09-2025": { - id: "google/gemini-2.5-flash-lite-preview-09-2025", - name: "Gemini 2.5 Flash Lite Preview 09-25", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-3.1-pro-preview-customtools": { - id: "google/gemini-3.1-pro-preview-customtools", - name: "Gemini 3.1 Pro Preview Custom Tools", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-19", - last_updated: "2026-02-19", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, reasoning: 12, context_over_200k: { input: 4, output: 18, cache_read: 0.4 } }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-2.5-pro-preview-06-05": { - id: "google/gemini-2.5-pro-preview-06-05", - name: "Gemini 2.5 Pro Preview 06-05", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-05", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemma-3n-e4b-it:free": { - id: "google/gemma-3n-e4b-it:free", - name: "Gemma 3n 4B (free)", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-06", - release_date: "2025-05-20", - last_updated: "2025-05-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 8192, output: 2000 }, - }, - "google/gemini-2.5-flash-preview-09-2025": { - id: "google/gemini-2.5-flash-preview-09-2025", - name: "Gemini 2.5 Flash Preview 09-25", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.031 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-2.5-pro-preview-05-06": { - id: "google/gemini-2.5-pro-preview-05-06", - name: "Gemini 2.5 Pro Preview 05-06", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-05-06", - last_updated: "2025-05-06", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemma-3n-e2b-it:free": { - id: "google/gemma-3n-e2b-it:free", - name: "Gemma 3n 2B (free)", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-06", - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 8192, output: 2000 }, - }, - "google/gemini-2.5-flash": { - id: "google/gemini-2.5-flash", - name: "Gemini 2.5 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-07-17", - last_updated: "2025-07-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.0375 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-2.0-flash-001": { - id: "google/gemini-2.0-flash-001", - name: "Gemini 2.0 Flash", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-06", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 1048576, output: 8192 }, - }, - "google/gemini-3.1-flash-lite-preview": { - id: "google/gemini-3.1-flash-lite-preview", - name: "Gemini 3.1 Flash Lite Preview", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-03-03", - last_updated: "2026-03-03", - modalities: { input: ["text", "image", "video", "pdf", "audio"], output: ["text"] }, - open_weights: false, - cost: { - input: 0.25, - output: 1.5, - reasoning: 1.5, - cache_read: 0.025, - cache_write: 0.083, - input_audio: 0.5, - output_audio: 0.5, - }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-3-flash-preview": { - id: "google/gemini-3-flash-preview", - name: "Gemini 3 Flash Preview", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 3, cache_read: 0.05 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemma-3-12b-it:free": { - id: "google/gemma-3-12b-it:free", - name: "Gemma 3 12B (free)", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2025-03-13", - last_updated: "2025-03-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 8192 }, - }, - "google/gemini-2.5-flash-lite": { - id: "google/gemini-2.5-flash-lite", - name: "Gemini 2.5 Flash Lite", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-3.1-pro-preview": { - id: "google/gemini-3.1-pro-preview", - name: "Gemini 3.1 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-19", - last_updated: "2026-02-19", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, reasoning: 12, context_over_200k: { input: 4, output: 18, cache_read: 0.4 } }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-2.0-flash-exp:free": { - id: "google/gemini-2.0-flash-exp:free", - name: "Gemini 2.0 Flash Experimental (free)", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 1048576, output: 1048576 }, - }, - "google/gemma-2-9b-it": { - id: "google/gemma-2-9b-it", - name: "Gemma 2 9B", - family: "gemma", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-06", - release_date: "2024-06-28", - last_updated: "2024-06-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.09 }, - limit: { context: 8192, output: 8192 }, - }, - "google/gemma-3-4b-it:free": { - id: "google/gemma-3-4b-it:free", - name: "Gemma 3 4B (free)", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2025-03-13", - last_updated: "2025-03-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 8192 }, - }, - "google/gemma-3n-e4b-it": { - id: "google/gemma-3n-e4b-it", - name: "Gemma 3n 4B", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-06", - release_date: "2025-05-20", - last_updated: "2025-05-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.04 }, - limit: { context: 32768, output: 32768 }, - }, - "google/gemini-3-pro-preview": { - id: "google/gemini-3-pro-preview", - name: "Gemini 3 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-11-18", - last_updated: "2025-11", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12 }, - limit: { context: 1050000, output: 66000 }, - }, - "google/gemma-3-12b-it": { - id: "google/gemma-3-12b-it", - name: "Gemma 3 12B", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-03-13", - last_updated: "2025-03-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.1 }, - limit: { context: 131072, output: 131072 }, - }, - "google/gemma-3-4b-it": { - id: "google/gemma-3-4b-it", - name: "Gemma 3 4B", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2025-03-13", - last_updated: "2025-03-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01703, output: 0.06815 }, - limit: { context: 96000, output: 96000 }, - }, - "google/gemma-3-27b-it": { - id: "google/gemma-3-27b-it", - name: "Gemma 3 27B", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-03-12", - last_updated: "2025-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.15 }, - limit: { context: 96000, output: 96000 }, - }, - "google/gemini-2.5-pro": { - id: "google/gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemma-3-27b-it:free": { - id: "google/gemma-3-27b-it:free", - name: "Gemma 3 27B (free)", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-03-12", - last_updated: "2025-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 8192 }, - }, - "z-ai/glm-5": { - id: "z-ai/glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2, cache_read: 0.2 }, - limit: { context: 202752, output: 131000 }, - }, - "z-ai/glm-4.5-air": { - id: "z-ai/glm-4.5-air", - name: "GLM 4.5 Air", - family: "glm-air", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 1.1 }, - limit: { context: 128000, output: 96000 }, - }, - "z-ai/glm-4.5": { - id: "z-ai/glm-4.5", - name: "GLM 4.5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 128000, output: 96000 }, - }, - "z-ai/glm-4.6:exacto": { - id: "z-ai/glm-4.6:exacto", - name: "GLM 4.6 (exacto)", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 1.9, cache_read: 0.11 }, - limit: { context: 200000, output: 128000 }, - }, - "z-ai/glm-4.7-flash": { - id: "z-ai/glm-4.7-flash", - name: "GLM-4.7-Flash", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - structured_output: true, - temperature: true, - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.4 }, - limit: { context: 200000, output: 65535 }, - }, - "z-ai/glm-4.5-air:free": { - id: "z-ai/glm-4.5-air:free", - name: "GLM 4.5 Air (free)", - family: "glm-air", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 96000 }, - }, - "z-ai/glm-4.6": { - id: "z-ai/glm-4.6", - name: "GLM 4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.11 }, - limit: { context: 200000, output: 128000 }, - }, - "z-ai/glm-4.7": { - id: "z-ai/glm-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.11 }, - limit: { context: 204800, output: 131072 }, - }, - "z-ai/glm-4.5v": { - id: "z-ai/glm-4.5v", - name: "GLM 4.5V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-08-11", - last_updated: "2025-08-11", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 1.8 }, - limit: { context: 64000, output: 16384 }, - }, - "qwen/qwen3-next-80b-a3b-thinking": { - id: "qwen/qwen3-next-80b-a3b-thinking", - name: "Qwen3 Next 80B A3B Thinking", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-11", - last_updated: "2025-09-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.14, output: 1.4 }, - limit: { context: 262144, output: 262144 }, - }, - "qwen/qwen-2.5-vl-7b-instruct:free": { - id: "qwen/qwen-2.5-vl-7b-instruct:free", - name: "Qwen2.5-VL 7B Instruct (free)", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-02", - release_date: "2024-08-28", - last_updated: "2024-08-28", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 32768 }, - }, - "qwen/qwen3-32b:free": { - id: "qwen/qwen3-32b:free", - name: "Qwen3 32B (free)", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-28", - last_updated: "2025-04-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 40960, output: 40960 }, - }, - "qwen/qwen3-coder:free": { - id: "qwen/qwen3-coder:free", - name: "Qwen3 Coder 480B A35B Instruct (free)", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 66536 }, - }, - "qwen/qwen3-coder-flash": { - id: "qwen/qwen3-coder-flash", - name: "Qwen3 Coder Flash", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.5 }, - limit: { context: 128000, output: 66536 }, - }, - "qwen/qwen3-30b-a3b:free": { - id: "qwen/qwen3-30b-a3b:free", - name: "Qwen3 30B A3B (free)", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-28", - last_updated: "2025-04-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 40960, output: 40960 }, - }, - "qwen/qwen3-235b-a22b-07-25:free": { - id: "qwen/qwen3-235b-a22b-07-25:free", - name: "Qwen3 235B A22B Instruct 2507 (free)", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-28", - last_updated: "2025-07-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 131072 }, - }, - "qwen/qwen3-14b:free": { - id: "qwen/qwen3-14b:free", - name: "Qwen3 14B (free)", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-28", - last_updated: "2025-04-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 40960, output: 40960 }, - }, - "qwen/qwen3-coder": { - id: "qwen/qwen3-coder", - name: "Qwen3 Coder", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 262144, output: 66536 }, - }, - "qwen/qwq-32b:free": { - id: "qwen/qwq-32b:free", - name: "QwQ 32B (free)", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-03-05", - last_updated: "2025-03-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 32768 }, - }, - "qwen/qwen3.5-397b-a17b": { - id: "qwen/qwen3.5-397b-a17b", - name: "Qwen3.5 397B A17B", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-16", - last_updated: "2026-02-16", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3.6 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen/qwen3-coder:exacto": { - id: "qwen/qwen3-coder:exacto", - name: "Qwen3 Coder (exacto)", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.38, output: 1.53 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen-2.5-coder-32b-instruct": { - id: "qwen/qwen-2.5-coder-32b-instruct", - name: "Qwen2.5 Coder 32B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-11-11", - last_updated: "2024-11-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 8192 }, - }, - "qwen/qwen3.5-plus-02-15": { - id: "qwen/qwen3.5-plus-02-15", - name: "Qwen3.5 Plus 2026-02-15", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-16", - last_updated: "2026-02-16", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2.4 }, - limit: { context: 1000000, output: 65536 }, - }, - "qwen/qwen3-30b-a3b-instruct-2507": { - id: "qwen/qwen3-30b-a3b-instruct-2507", - name: "Qwen3 30B A3B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-29", - last_updated: "2025-07-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 262000, output: 262000 }, - }, - "qwen/qwen2.5-vl-72b-instruct": { - id: "qwen/qwen2.5-vl-72b-instruct", - name: "Qwen2.5 VL 72B Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-02-01", - last_updated: "2025-02-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 8192 }, - }, - "qwen/qwen3-coder-30b-a3b-instruct": { - id: "qwen/qwen3-coder-30b-a3b-instruct", - name: "Qwen3 Coder 30B A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-31", - last_updated: "2025-07-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.27 }, - limit: { context: 160000, output: 65536 }, - }, - "qwen/qwen3-235b-a22b-07-25": { - id: "qwen/qwen3-235b-a22b-07-25", - name: "Qwen3 235B A22B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-28", - last_updated: "2025-07-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.85 }, - limit: { context: 262144, output: 131072 }, - }, - "qwen/qwen3-235b-a22b:free": { - id: "qwen/qwen3-235b-a22b:free", - name: "Qwen3 235B A22B (free)", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-28", - last_updated: "2025-04-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 131072 }, - }, - "qwen/qwen3-next-80b-a3b-instruct:free": { - id: "qwen/qwen3-next-80b-a3b-instruct:free", - name: "Qwen3 Next 80B A3B Instruct (free)", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-11", - last_updated: "2025-09-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 262144 }, - }, - "qwen/qwen3-4b:free": { - id: "qwen/qwen3-4b:free", - name: "Qwen3 4B (free)", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-30", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 40960, output: 40960 }, - }, - "qwen/qwen3-8b:free": { - id: "qwen/qwen3-8b:free", - name: "Qwen3 8B (free)", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-28", - last_updated: "2025-04-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 40960, output: 40960 }, - }, - "qwen/qwen3-30b-a3b-thinking-2507": { - id: "qwen/qwen3-30b-a3b-thinking-2507", - name: "Qwen3 30B A3B Thinking 2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-29", - last_updated: "2025-07-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 262000, output: 262000 }, - }, - "qwen/qwen2.5-vl-32b-instruct:free": { - id: "qwen/qwen2.5-vl-32b-instruct:free", - name: "Qwen2.5 VL 32B Instruct (free)", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-03-24", - last_updated: "2025-03-24", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 8192, output: 8192 }, - }, - "qwen/qwen3-235b-a22b-thinking-2507": { - id: "qwen/qwen3-235b-a22b-thinking-2507", - name: "Qwen3 235B A22B Thinking 2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-25", - last_updated: "2025-07-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.078, output: 0.312 }, - limit: { context: 262144, output: 81920 }, - }, - "qwen/qwen3-next-80b-a3b-instruct": { - id: "qwen/qwen3-next-80b-a3b-instruct", - name: "Qwen3 Next 80B A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-11", - last_updated: "2025-09-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.14, output: 1.4 }, - limit: { context: 262144, output: 262144 }, - }, - "qwen/qwen2.5-vl-72b-instruct:free": { - id: "qwen/qwen2.5-vl-72b-instruct:free", - name: "Qwen2.5 VL 72B Instruct (free)", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-02", - release_date: "2025-02-01", - last_updated: "2025-02-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 32768 }, - }, - "qwen/qwen3-max": { - id: "qwen/qwen3-max", - name: "Qwen3 Max", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.2, output: 6 }, - limit: { context: 262144, output: 32768 }, - }, - "x-ai/grok-3": { - id: "x-ai/grok-3", - name: "Grok 3", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75, cache_write: 15 }, - limit: { context: 131072, output: 8192 }, - }, - "x-ai/grok-code-fast-1": { - id: "x-ai/grok-code-fast-1", - name: "Grok Code Fast 1", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-08-26", - last_updated: "2025-08-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.5, cache_read: 0.02 }, - limit: { context: 256000, output: 10000 }, - }, - "x-ai/grok-4-fast": { - id: "x-ai/grok-4-fast", - name: "Grok 4 Fast", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-08-19", - last_updated: "2025-08-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05, cache_write: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "x-ai/grok-4": { - id: "x-ai/grok-4", - name: "Grok 4", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75, cache_write: 15 }, - limit: { context: 256000, output: 64000 }, - }, - "x-ai/grok-4.1-fast": { - id: "x-ai/grok-4.1-fast", - name: "Grok 4.1 Fast", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05, cache_write: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "x-ai/grok-3-mini-beta": { - id: "x-ai/grok-3-mini-beta", - name: "Grok 3 Mini Beta", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.5, cache_read: 0.075, cache_write: 0.5 }, - limit: { context: 131072, output: 8192 }, - }, - "x-ai/grok-3-mini": { - id: "x-ai/grok-3-mini", - name: "Grok 3 Mini", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.5, cache_read: 0.075, cache_write: 0.5 }, - limit: { context: 131072, output: 8192 }, - }, - "x-ai/grok-4.20-multi-agent-beta": { - id: "x-ai/grok-4.20-multi-agent-beta", - name: "Grok 4.20 Multi - Agent Beta", - family: "grok", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2026-03-12", - last_updated: "2026-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6, cache_read: 0.2, context_over_200k: { input: 4, output: 12 } }, - limit: { context: 2000000, output: 30000 }, - status: "beta", - }, - "x-ai/grok-4.20-beta": { - id: "x-ai/grok-4.20-beta", - name: "Grok 4.20 Beta", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-12", - last_updated: "2026-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6, cache_read: 0.2, context_over_200k: { input: 4, output: 12 } }, - limit: { context: 2000000, output: 30000 }, - status: "beta", - }, - "x-ai/grok-3-beta": { - id: "x-ai/grok-3-beta", - name: "Grok 3 Beta", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75, cache_write: 15 }, - limit: { context: 131072, output: 8192 }, - }, - "meta-llama/llama-3.3-70b-instruct:free": { - id: "meta-llama/llama-3.3-70b-instruct:free", - name: "Llama 3.3 70B Instruct (free)", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 131072 }, - }, - "meta-llama/llama-4-scout:free": { - id: "meta-llama/llama-4-scout:free", - name: "Llama 4 Scout (free)", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 64000, output: 64000 }, - }, - "meta-llama/llama-3.2-11b-vision-instruct": { - id: "meta-llama/llama-3.2-11b-vision-instruct", - name: "Llama 3.2 11B Vision Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 8192 }, - }, - "meta-llama/llama-3.2-3b-instruct:free": { - id: "meta-llama/llama-3.2-3b-instruct:free", - name: "Llama 3.2 3B Instruct (free)", - family: "llama", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 131072 }, - }, - "meta-llama/llama-3.1-405b-instruct:free": { - id: "meta-llama/llama-3.1-405b-instruct:free", - name: "Llama 3.1 405B Instruct (free)", - family: "llama", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-07-23", - last_updated: "2025-04-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 131072 }, - }, - "tngtech/tng-r1t-chimera:free": { - id: "tngtech/tng-r1t-chimera:free", - name: "R1T Chimera (free)", - family: "tngtech", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-11-26", - last_updated: "2026-01-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 163840, output: 163840 }, - }, - "tngtech/deepseek-r1t2-chimera:free": { - id: "tngtech/deepseek-r1t2-chimera:free", - name: "DeepSeek R1T2 Chimera (free)", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-08", - last_updated: "2025-07-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 163840, output: 163840 }, - }, - "mistralai/devstral-medium-2507": { - id: "mistralai/devstral-medium-2507", - name: "Devstral Medium", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-07-10", - last_updated: "2025-07-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2 }, - limit: { context: 131072, output: 131072 }, - }, - "mistralai/devstral-small-2505:free": { - id: "mistralai/devstral-small-2505:free", - name: "Devstral Small 2505 (free)", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-21", - last_updated: "2025-05-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 32768 }, - }, - "mistralai/mistral-medium-3": { - id: "mistralai/mistral-medium-3", - name: "Mistral Medium 3", - family: "mistral-medium", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-07", - last_updated: "2025-05-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 131072, output: 131072 }, - }, - "mistralai/codestral-2508": { - id: "mistralai/codestral-2508", - name: "Codestral 2508", - family: "codestral", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-08-01", - last_updated: "2025-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 256000, output: 256000 }, - }, - "mistralai/devstral-2512:free": { - id: "mistralai/devstral-2512:free", - name: "Devstral 2 2512 (free)", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-12", - release_date: "2025-09-12", - last_updated: "2025-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 262144 }, - }, - "mistralai/mistral-small-3.1-24b-instruct": { - id: "mistralai/mistral-small-3.1-24b-instruct", - name: "Mistral Small 3.1 24B Instruct", - family: "mistral-small", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-03-17", - last_updated: "2025-03-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "mistralai/devstral-small-2505": { - id: "mistralai/devstral-small-2505", - name: "Devstral Small", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-07", - last_updated: "2025-05-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.12 }, - limit: { context: 128000, output: 128000 }, - }, - "mistralai/mistral-7b-instruct:free": { - id: "mistralai/mistral-7b-instruct:free", - name: "Mistral 7B Instruct (free)", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-05", - release_date: "2024-05-27", - last_updated: "2024-05-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 32768 }, - }, - "mistralai/devstral-2512": { - id: "mistralai/devstral-2512", - name: "Devstral 2 2512", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-12", - release_date: "2025-09-12", - last_updated: "2025-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 262144, output: 262144 }, - }, - "mistralai/mistral-small-3.2-24b-instruct": { - id: "mistralai/mistral-small-3.2-24b-instruct", - name: "Mistral Small 3.2 24B Instruct", - family: "mistral-small", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-06-20", - last_updated: "2025-06-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 96000, output: 8192 }, - }, - "mistralai/mistral-small-3.2-24b-instruct:free": { - id: "mistralai/mistral-small-3.2-24b-instruct:free", - name: "Mistral Small 3.2 24B (free)", - family: "mistral-small", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-06-20", - last_updated: "2025-06-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 96000, output: 96000 }, - }, - "mistralai/devstral-small-2507": { - id: "mistralai/devstral-small-2507", - name: "Devstral Small 1.1", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-07-10", - last_updated: "2025-07-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 131072, output: 131072 }, - }, - "mistralai/mistral-nemo:free": { - id: "mistralai/mistral-nemo:free", - name: "Mistral Nemo (free)", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-07-19", - last_updated: "2024-07-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 131072 }, - }, - "mistralai/mistral-medium-3.1": { - id: "mistralai/mistral-medium-3.1", - name: "Mistral Medium 3.1", - family: "mistral-medium", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-08-12", - last_updated: "2025-08-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 262144, output: 262144 }, - }, - "openai/gpt-5.3-codex": { - id: "openai/gpt-5.3-codex", - name: "GPT-5.3-Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-02-24", - last_updated: "2026-02-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5-codex": { - id: "openai/gpt-5-codex", - name: "GPT-5 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10-01", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5-pro": { - id: "openai/gpt-5-pro", - name: "GPT-5 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-10-06", - last_updated: "2025-10-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 120 }, - limit: { context: 400000, output: 272000 }, - }, - "openai/gpt-4o-mini": { - id: "openai/gpt-4o-mini", - name: "GPT-4o-mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.08 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5.1-codex-max": { - id: "openai/gpt-5.1-codex-max", - name: "GPT-5.1-Codex-Max", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 9, cache_read: 0.11 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.2-codex": { - id: "openai/gpt-5.2-codex", - name: "GPT-5.2-Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-08-31", - release_date: "2026-01-14", - last_updated: "2026-01-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-oss-120b:exacto": { - id: "openai/gpt-oss-120b:exacto", - name: "GPT OSS 120B (exacto)", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.24 }, - limit: { context: 131072, output: 32768 }, - }, - "openai/gpt-5.1": { - id: "openai/gpt-5.1", - name: "GPT-5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.2-chat": { - id: "openai/gpt-5.2-chat", - name: "GPT-5.2 Chat", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5-chat": { - id: "openai/gpt-5-chat", - name: "GPT-5 Chat (latest)", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.1-chat": { - id: "openai/gpt-5.1-chat", - name: "GPT-5.1 Chat", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5-image": { - id: "openai/gpt-5-image", - name: "GPT-5 Image", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10-01", - release_date: "2025-10-14", - last_updated: "2025-10-14", - modalities: { input: ["text", "image", "pdf"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 5, output: 10, cache_read: 1.25 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.072, output: 0.28 }, - limit: { context: 131072, output: 32768 }, - }, - "openai/gpt-5.1-codex-mini": { - id: "openai/gpt-5.1-codex-mini", - name: "GPT-5.1-Codex-Mini", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.025 }, - limit: { context: 400000, output: 100000 }, - }, - "openai/gpt-5.2": { - id: "openai/gpt-5.2", - name: "GPT-5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-oss-20b:free": { - id: "openai/gpt-oss-20b:free", - name: "gpt-oss-20b (free)", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2026-01-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 32768 }, - }, - "openai/gpt-4.1": { - id: "openai/gpt-4.1", - name: "GPT-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/gpt-5": { - id: "openai/gpt-5", - name: "GPT-5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10-01", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/o4-mini": { - id: "openai/o4-mini", - name: "o4 Mini", - family: "o-mini", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-06", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.28 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4.1-mini": { - id: "openai/gpt-4.1-mini", - name: "GPT-4.1 Mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.6, cache_read: 0.1 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/gpt-5.4": { - id: "openai/gpt-5.4", - name: "GPT-5.4", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 2.5, - output: 15, - cache_read: 0.25, - context_over_200k: { input: 5, output: 22.5, cache_read: 0.5 }, - }, - limit: { context: 1050000, input: 922000, output: 128000 }, - }, - "openai/gpt-5.4-pro": { - id: "openai/gpt-5.4-pro", - name: "GPT-5.4 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 30, output: 180, cache_read: 30 }, - limit: { context: 1050000, input: 922000, output: 128000 }, - }, - "openai/gpt-oss-safeguard-20b": { - id: "openai/gpt-oss-safeguard-20b", - name: "GPT OSS Safeguard 20B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-10-29", - last_updated: "2025-10-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 131072, output: 65536 }, - }, - "openai/gpt-5.1-codex": { - id: "openai/gpt-5.1-codex", - name: "GPT-5.1-Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.2-pro": { - id: "openai/gpt-5.2-pro", - name: "GPT-5.2 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 21, output: 168 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5-mini": { - id: "openai/gpt-5-mini", - name: "GPT-5 Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10-01", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "GPT OSS 20B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.2 }, - limit: { context: 131072, output: 32768 }, - }, - "openai/gpt-oss-120b:free": { - id: "openai/gpt-oss-120b:free", - name: "gpt-oss-120b (free)", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 32768 }, - }, - "openai/gpt-5-nano": { - id: "openai/gpt-5-nano", - name: "GPT-5 Nano", - family: "gpt-nano", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10-01", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4 }, - limit: { context: 400000, output: 128000 }, - }, - "minimax/minimax-m1": { - id: "minimax/minimax-m1", - name: "MiniMax M1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2.2 }, - limit: { context: 1000000, output: 40000 }, - }, - "minimax/minimax-01": { - id: "minimax/minimax-01", - name: "MiniMax-01", - family: "minimax", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-01-15", - last_updated: "2025-01-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 1.1 }, - limit: { context: 1000000, output: 1000000 }, - }, - "minimax/minimax-m2.1": { - id: "minimax/minimax-m2.1", - name: "MiniMax M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - structured_output: true, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 204800, output: 131072 }, - }, - "minimax/minimax-m2": { - id: "minimax/minimax-m2", - name: "MiniMax M2", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - structured_output: true, - temperature: true, - release_date: "2025-10-23", - last_updated: "2025-10-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 1.15, cache_read: 0.28, cache_write: 1.15 }, - limit: { context: 196600, output: 118000 }, - }, - "minimax/minimax-m2.5": { - id: "minimax/minimax-m2.5", - name: "MiniMax M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - structured_output: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.03 }, - limit: { context: 204800, output: 131072 }, - }, - "bytedance-seed/seedream-4.5": { - id: "bytedance-seed/seedream-4.5", - name: "Seedream 4.5", - family: "seed", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2025-12-23", - last_updated: "2026-01-31", - modalities: { input: ["image", "text"], output: ["image"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 4096, output: 4096 }, - }, - "anthropic/claude-3.7-sonnet": { - id: "anthropic/claude-3.7-sonnet", - name: "Claude Sonnet 3.7", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-01", - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 128000 }, - }, - "anthropic/claude-opus-4.1": { - id: "anthropic/claude-opus-4.1", - name: "Claude Opus 4.1", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "anthropic/claude-sonnet-4.6": { - id: "anthropic/claude-sonnet-4.6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 1000000, output: 128000 }, - }, - "anthropic/claude-haiku-4.5": { - id: "anthropic/claude-haiku-4.5", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-3.5-haiku": { - id: "anthropic/claude-3.5-haiku", - name: "Claude Haiku 3.5", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07-31", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 }, - limit: { context: 200000, output: 8192 }, - }, - "anthropic/claude-opus-4.5": { - id: "anthropic/claude-opus-4.5", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-05-30", - release_date: "2025-11-24", - last_updated: "2025-11-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 32000 }, - }, - "anthropic/claude-opus-4": { - id: "anthropic/claude-opus-4", - name: "Claude Opus 4", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "anthropic/claude-sonnet-4": { - id: "anthropic/claude-sonnet-4", - name: "Claude Sonnet 4", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-sonnet-4.5": { - id: "anthropic/claude-sonnet-4.5", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 1000000, output: 64000 }, - }, - "anthropic/claude-opus-4.6": { - id: "anthropic/claude-opus-4.6", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-05-30", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 5, - output: 25, - cache_read: 0.5, - cache_write: 6.25, - context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 }, - }, - limit: { context: 1000000, output: 128000 }, - }, - "black-forest-labs/flux.2-pro": { - id: "black-forest-labs/flux.2-pro", - name: "FLUX.2 Pro", - family: "flux", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2025-11-25", - last_updated: "2026-01-31", - modalities: { input: ["image", "text"], output: ["image"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 46864, output: 46864 }, - }, - "black-forest-labs/flux.2-flex": { - id: "black-forest-labs/flux.2-flex", - name: "FLUX.2 Flex", - family: "flux", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2025-11-25", - last_updated: "2026-01-31", - modalities: { input: ["image", "text"], output: ["image"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 67344, output: 67344 }, - }, - "black-forest-labs/flux.2-max": { - id: "black-forest-labs/flux.2-max", - name: "FLUX.2 Max", - family: "flux", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2025-12-16", - last_updated: "2026-01-31", - modalities: { input: ["image", "text"], output: ["image"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 46864, output: 46864 }, - }, - "black-forest-labs/flux.2-klein-4b": { - id: "black-forest-labs/flux.2-klein-4b", - name: "FLUX.2 Klein 4B", - family: "flux", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2026-01-14", - last_updated: "2026-01-31", - modalities: { input: ["image", "text"], output: ["image"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 40960, output: 40960 }, - }, - "nousresearch/hermes-4-405b": { - id: "nousresearch/hermes-4-405b", - name: "Hermes 4 405B", - family: "hermes", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2025-08-25", - last_updated: "2025-08-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3 }, - limit: { context: 131072, output: 131072 }, - }, - "nousresearch/hermes-4-70b": { - id: "nousresearch/hermes-4-70b", - name: "Hermes 4 70B", - family: "hermes", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-12", - release_date: "2025-08-25", - last_updated: "2025-08-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.4 }, - limit: { context: 131072, output: 131072 }, - }, - "nousresearch/deephermes-3-llama-3-8b-preview": { - id: "nousresearch/deephermes-3-llama-3-8b-preview", - name: "DeepHermes 3 Llama 3 8B Preview", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-02-28", - last_updated: "2025-02-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 8192 }, - }, - "nousresearch/hermes-3-llama-3.1-405b:free": { - id: "nousresearch/hermes-3-llama-3.1-405b:free", - name: "Hermes 3 405B Instruct (free)", - family: "hermes", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2023-12", - release_date: "2024-08-16", - last_updated: "2024-08-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131072, output: 131072 }, - }, - }, - }, - "github-models": { - id: "github-models", - env: ["GITHUB_TOKEN"], - npm: "@ai-sdk/openai-compatible", - api: "https://models.github.ai/inference", - name: "GitHub Models", - doc: "https://docs.github.com/en/github-models", - models: { - "ai21-labs/ai21-jamba-1.5-mini": { - id: "ai21-labs/ai21-jamba-1.5-mini", - name: "AI21 Jamba 1.5 Mini", - family: "jamba", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2024-08-29", - last_updated: "2024-08-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 4096 }, - }, - "ai21-labs/ai21-jamba-1.5-large": { - id: "ai21-labs/ai21-jamba-1.5-large", - name: "AI21 Jamba 1.5 Large", - family: "jamba", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2024-08-29", - last_updated: "2024-08-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 4096 }, - }, - "microsoft/phi-4-multimodal-instruct": { - id: "microsoft/phi-4-multimodal-instruct", - name: "Phi-4-multimodal-instruct", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-3-small-128k-instruct": { - id: "microsoft/phi-3-small-128k-instruct", - name: "Phi-3-small instruct (128k)", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-3-medium-128k-instruct": { - id: "microsoft/phi-3-medium-128k-instruct", - name: "Phi-3-medium instruct (128k)", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/mai-ds-r1": { - id: "microsoft/mai-ds-r1", - name: "MAI-DS-R1", - family: "mai", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 65536, output: 8192 }, - }, - "microsoft/phi-3.5-moe-instruct": { - id: "microsoft/phi-3.5-moe-instruct", - name: "Phi-3.5-MoE instruct (128k)", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-08-20", - last_updated: "2024-08-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-3.5-mini-instruct": { - id: "microsoft/phi-3.5-mini-instruct", - name: "Phi-3.5-mini instruct (128k)", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-08-20", - last_updated: "2024-08-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-4-mini-instruct": { - id: "microsoft/phi-4-mini-instruct", - name: "Phi-4-mini-instruct", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-4": { - id: "microsoft/phi-4", - name: "Phi-4", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 16000, output: 4096 }, - }, - "microsoft/phi-3-mini-4k-instruct": { - id: "microsoft/phi-3-mini-4k-instruct", - name: "Phi-3-mini instruct (4k)", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 4096, output: 1024 }, - }, - "microsoft/phi-4-mini-reasoning": { - id: "microsoft/phi-4-mini-reasoning", - name: "Phi-4-mini-reasoning", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-3.5-vision-instruct": { - id: "microsoft/phi-3.5-vision-instruct", - name: "Phi-3.5-vision instruct (128k)", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-08-20", - last_updated: "2024-08-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-3-medium-4k-instruct": { - id: "microsoft/phi-3-medium-4k-instruct", - name: "Phi-3-medium instruct (4k)", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 4096, output: 1024 }, - }, - "microsoft/phi-3-mini-128k-instruct": { - id: "microsoft/phi-3-mini-128k-instruct", - name: "Phi-3-mini instruct (128k)", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-4-reasoning": { - id: "microsoft/phi-4-reasoning", - name: "Phi-4-Reasoning", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "microsoft/phi-3-small-8k-instruct": { - id: "microsoft/phi-3-small-8k-instruct", - name: "Phi-3-small instruct (8k)", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 8192, output: 2048 }, - }, - "core42/jais-30b-chat": { - id: "core42/jais-30b-chat", - name: "JAIS 30b Chat", - family: "jais", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-03", - release_date: "2023-08-30", - last_updated: "2023-08-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 8192, output: 2048 }, - }, - "mistral-ai/ministral-3b": { - id: "mistral-ai/ministral-3b", - name: "Ministral 3B", - family: "ministral", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "mistral-ai/mistral-medium-2505": { - id: "mistral-ai/mistral-medium-2505", - name: "Mistral Medium 3 (25.05)", - family: "mistral-medium", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2025-05-01", - last_updated: "2025-05-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - "mistral-ai/mistral-nemo": { - id: "mistral-ai/mistral-nemo", - name: "Mistral Nemo", - family: "mistral-nemo", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "mistral-ai/mistral-large-2411": { - id: "mistral-ai/mistral-large-2411", - name: "Mistral Large 24.11", - family: "mistral-large", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2024-11-01", - last_updated: "2024-11-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - "mistral-ai/mistral-small-2503": { - id: "mistral-ai/mistral-small-2503", - name: "Mistral Small 3.1", - family: "mistral-small", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2025-03-01", - last_updated: "2025-03-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - "mistral-ai/codestral-2501": { - id: "mistral-ai/codestral-2501", - name: "Codestral 25.01", - family: "codestral", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 32000, output: 8192 }, - }, - "deepseek/deepseek-r1-0528": { - id: "deepseek/deepseek-r1-0528", - name: "DeepSeek-R1-0528", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 65536, output: 8192 }, - }, - "deepseek/deepseek-r1": { - id: "deepseek/deepseek-r1", - name: "DeepSeek-R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 65536, output: 8192 }, - }, - "deepseek/deepseek-v3-0324": { - id: "deepseek/deepseek-v3-0324", - name: "DeepSeek-V3-0324", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2025-03-24", - last_updated: "2025-03-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "meta/llama-3.2-90b-vision-instruct": { - id: "meta/llama-3.2-90b-vision-instruct", - name: "Llama-3.2-90B-Vision-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "meta/llama-3.3-70b-instruct": { - id: "meta/llama-3.3-70b-instruct", - name: "Llama-3.3-70B-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - "meta/llama-4-scout-17b-16e-instruct": { - id: "meta/llama-4-scout-17b-16e-instruct", - name: "Llama 4 Scout 17B 16E Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-31", - last_updated: "2025-01-31", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "meta/meta-llama-3.1-405b-instruct": { - id: "meta/meta-llama-3.1-405b-instruct", - name: "Meta-Llama-3.1-405B-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - "meta/meta-llama-3-8b-instruct": { - id: "meta/meta-llama-3-8b-instruct", - name: "Meta-Llama-3-8B-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-04-18", - last_updated: "2024-04-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 8192, output: 2048 }, - }, - "meta/llama-3.2-11b-vision-instruct": { - id: "meta/llama-3.2-11b-vision-instruct", - name: "Llama-3.2-11B-Vision-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "meta/meta-llama-3-70b-instruct": { - id: "meta/meta-llama-3-70b-instruct", - name: "Meta-Llama-3-70B-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-04-18", - last_updated: "2024-04-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 8192, output: 2048 }, - }, - "meta/meta-llama-3.1-70b-instruct": { - id: "meta/meta-llama-3.1-70b-instruct", - name: "Meta-Llama-3.1-70B-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - "meta/meta-llama-3.1-8b-instruct": { - id: "meta/meta-llama-3.1-8b-instruct", - name: "Meta-Llama-3.1-8B-Instruct", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - "meta/llama-4-maverick-17b-128e-instruct-fp8": { - id: "meta/llama-4-maverick-17b-128e-instruct-fp8", - name: "Llama 4 Maverick 17B 128E Instruct FP8", - family: "llama", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-31", - last_updated: "2025-01-31", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "openai/gpt-4o-mini": { - id: "openai/gpt-4o-mini", - name: "GPT-4o mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/o1": { - id: "openai/o1", - name: "OpenAI o1", - family: "o", - attachment: false, - reasoning: true, - tool_call: false, - temperature: false, - knowledge: "2023-10", - release_date: "2024-09-12", - last_updated: "2024-12-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/o3": { - id: "openai/o3", - name: "OpenAI o3", - family: "o", - attachment: false, - reasoning: true, - tool_call: false, - temperature: false, - knowledge: "2024-04", - release_date: "2025-01-31", - last_updated: "2025-01-31", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4.1-nano": { - id: "openai/gpt-4.1-nano", - name: "GPT-4.1-nano", - family: "gpt-nano", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-4.1": { - id: "openai/gpt-4.1", - name: "GPT-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/o4-mini": { - id: "openai/o4-mini", - name: "OpenAI o4-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: false, - temperature: false, - knowledge: "2024-04", - release_date: "2025-01-31", - last_updated: "2025-01-31", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4.1-mini": { - id: "openai/gpt-4.1-mini", - name: "GPT-4.1-mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/o1-preview": { - id: "openai/o1-preview", - name: "OpenAI o1-preview", - family: "o", - attachment: false, - reasoning: true, - tool_call: false, - temperature: false, - knowledge: "2023-10", - release_date: "2024-09-12", - last_updated: "2024-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - "openai/o3-mini": { - id: "openai/o3-mini", - name: "OpenAI o3-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: false, - temperature: false, - knowledge: "2024-04", - release_date: "2025-01-31", - last_updated: "2025-01-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/o1-mini": { - id: "openai/o1-mini", - name: "OpenAI o1-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: false, - temperature: false, - knowledge: "2023-10", - release_date: "2024-09-12", - last_updated: "2024-12-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 65536 }, - }, - "openai/gpt-4o": { - id: "openai/gpt-4o", - name: "GPT-4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "cohere/cohere-command-a": { - id: "cohere/cohere-command-a", - name: "Cohere Command A", - family: "command-a", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2024-11-01", - last_updated: "2024-11-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "cohere/cohere-command-r-plus-08-2024": { - id: "cohere/cohere-command-r-plus-08-2024", - name: "Cohere Command R+ 08-2024", - family: "command-r", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2024-08-01", - last_updated: "2024-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "cohere/cohere-command-r": { - id: "cohere/cohere-command-r", - name: "Cohere Command R", - family: "command-r", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2024-03-11", - last_updated: "2024-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "cohere/cohere-command-r-08-2024": { - id: "cohere/cohere-command-r-08-2024", - name: "Cohere Command R 08-2024", - family: "command-r", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2024-08-01", - last_updated: "2024-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "cohere/cohere-command-r-plus": { - id: "cohere/cohere-command-r-plus", - name: "Cohere Command R+", - family: "command-r", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2024-04-04", - last_updated: "2024-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "xai/grok-3": { - id: "xai/grok-3", - name: "Grok 3", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-09", - last_updated: "2024-12-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "xai/grok-3-mini": { - id: "xai/grok-3-mini", - name: "Grok 3 Mini", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-09", - last_updated: "2024-12-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - }, - }, - "302ai": { - id: "302ai", - env: ["302AI_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.302.ai/v1", - name: "302.AI", - doc: "https://doc.302.ai", - models: { - "qwen3-235b-a22b-instruct-2507": { - id: "qwen3-235b-a22b-instruct-2507", - name: "qwen3-235b-a22b-instruct-2507", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-30", - last_updated: "2025-07-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 1.143 }, - limit: { context: 128000, output: 65536 }, - }, - "gpt-5-pro": { - id: "gpt-5-pro", - name: "gpt-5-pro", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-10-08", - last_updated: "2025-10-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 120 }, - limit: { context: 400000, output: 272000 }, - }, - "claude-opus-4-5-20251101": { - id: "claude-opus-4-5-20251101", - name: "claude-opus-4-5-20251101", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-11-25", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25 }, - limit: { context: 200000, output: 64000 }, - }, - "deepseek-reasoner": { - id: "deepseek-reasoner", - name: "Deepseek-Reasoner", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 0.43 }, - limit: { context: 128000, output: 128000 }, - }, - "qwen-max-latest": { - id: "qwen-max-latest", - name: "Qwen-Max-Latest", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2024-04-03", - last_updated: "2025-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.343, output: 1.372 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-max-2025-09-23": { - id: "qwen3-max-2025-09-23", - name: "qwen3-max-2025-09-23", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-24", - last_updated: "2025-09-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.86, output: 3.43 }, - limit: { context: 258048, output: 65536 }, - }, - "grok-4-fast-reasoning": { - id: "grok-4-fast-reasoning", - name: "grok-4-fast-reasoning", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-09-23", - last_updated: "2025-09-23", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5 }, - limit: { context: 2000000, output: 30000 }, - }, - "gemini-2.5-flash-lite-preview-09-2025": { - id: "gemini-2.5-flash-lite-preview-09-2025", - name: "gemini-2.5-flash-lite-preview-09-2025", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-26", - last_updated: "2025-09-26", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 1000000, output: 65536 }, - }, - "gpt-5.2-chat-latest": { - id: "gpt-5.2-chat-latest", - name: "gpt-5.2-chat-latest", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-12-12", - last_updated: "2025-12-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 128000, output: 16384 }, - }, - "claude-opus-4-1-20250805-thinking": { - id: "claude-opus-4-1-20250805-thinking", - name: "claude-opus-4-1-20250805-thinking", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-05-27", - last_updated: "2025-05-27", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75 }, - limit: { context: 200000, output: 32000 }, - }, - "qwen3-coder-480b-a35b-instruct": { - id: "qwen3-coder-480b-a35b-instruct", - name: "qwen3-coder-480b-a35b-instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.86, output: 3.43 }, - limit: { context: 262144, output: 65536 }, - }, - "gemini-2.5-flash-preview-09-2025": { - id: "gemini-2.5-flash-preview-09-2025", - name: "gemini-2.5-flash-preview-09-2025", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-26", - last_updated: "2025-09-26", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5 }, - limit: { context: 1000000, output: 65536 }, - }, - "grok-4-1-fast-reasoning": { - id: "grok-4-1-fast-reasoning", - name: "grok-4-1-fast-reasoning", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-11-20", - last_updated: "2025-11-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5 }, - limit: { context: 2000000, output: 30000 }, - }, - "glm-4.5": { - id: "glm-4.5", - name: "GLM-4.5", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-29", - last_updated: "2025-07-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.286, output: 1.142 }, - limit: { context: 128000, output: 98304 }, - }, - "gemini-2.5-flash": { - id: "gemini-2.5-flash", - name: "gemini-2.5-flash", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5 }, - limit: { context: 1000000, output: 65536 }, - }, - "kimi-k2-0905-preview": { - id: "kimi-k2-0905-preview", - name: "kimi-k2-0905-preview", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.632, output: 2.53 }, - limit: { context: 262144, output: 262144 }, - }, - "grok-4-1-fast-non-reasoning": { - id: "grok-4-1-fast-non-reasoning", - name: "grok-4-1-fast-non-reasoning", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-11-20", - last_updated: "2025-11-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5 }, - limit: { context: 2000000, output: 30000 }, - }, - "gpt-5.1": { - id: "gpt-5.1", - name: "gpt-5.1", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, output: 128000 }, - }, - "claude-sonnet-4-5-20250929-thinking": { - id: "claude-sonnet-4-5-20250929-thinking", - name: "claude-sonnet-4-5-20250929-thinking", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, output: 64000 }, - }, - "mistral-large-2512": { - id: "mistral-large-2512", - name: "mistral-large-2512", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-12-16", - last_updated: "2025-12-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 3.3 }, - limit: { context: 128000, output: 262144 }, - }, - "glm-4.6": { - id: "glm-4.6", - name: "glm-4.6", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.286, output: 1.142 }, - limit: { context: 200000, output: 131072 }, - }, - "gemini-3-flash-preview": { - id: "gemini-3-flash-preview", - name: "gemini-3-flash-preview", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-12-18", - last_updated: "2025-12-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 3 }, - limit: { context: 1000000, output: 65536 }, - }, - "gpt-4.1-nano": { - id: "gpt-4.1-nano", - name: "gpt-4.1-nano", - family: "gpt-nano", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 1000000, output: 32768 }, - }, - "doubao-seed-1-6-vision-250815": { - id: "doubao-seed-1-6-vision-250815", - name: "doubao-seed-1-6-vision-250815", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.114, output: 1.143 }, - limit: { context: 256000, output: 32000 }, - }, - "doubao-seed-1-6-thinking-250715": { - id: "doubao-seed-1-6-thinking-250715", - name: "doubao-seed-1-6-thinking-250715", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-15", - last_updated: "2025-07-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.121, output: 1.21 }, - limit: { context: 256000, output: 16000 }, - }, - "doubao-seed-1-8-251215": { - id: "doubao-seed-1-8-251215", - name: "doubao-seed-1-8-251215", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-12-18", - last_updated: "2025-12-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.114, output: 0.286 }, - limit: { context: 224000, output: 64000 }, - }, - "claude-sonnet-4-5-20250929": { - id: "claude-sonnet-4-5-20250929", - name: "claude-sonnet-4-5-20250929", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, output: 64000 }, - }, - "ministral-14b-2512": { - id: "ministral-14b-2512", - name: "ministral-14b-2512", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-12-16", - last_updated: "2025-12-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.33, output: 0.33 }, - limit: { context: 128000, output: 128000 }, - }, - "MiniMax-M2": { - id: "MiniMax-M2", - name: "MiniMax-M2", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-10-26", - last_updated: "2025-10-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.33, output: 1.32 }, - limit: { context: 1000000, output: 128000 }, - }, - "gpt-5.2": { - id: "gpt-5.2", - name: "gpt-5.2", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-12-12", - last_updated: "2025-12-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-4.1": { - id: "gpt-4.1", - name: "gpt-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8 }, - limit: { context: 1000000, output: 32768 }, - }, - "gemini-2.5-flash-nothink": { - id: "gemini-2.5-flash-nothink", - name: "gemini-2.5-flash-nothink", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-24", - last_updated: "2025-06-24", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5 }, - limit: { context: 1000000, output: 65536 }, - }, - "qwen3-235b-a22b": { - id: "qwen3-235b-a22b", - name: "Qwen3-235B-A22B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-29", - last_updated: "2025-04-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 2.86 }, - limit: { context: 128000, output: 16384 }, - }, - "deepseek-v3.2": { - id: "deepseek-v3.2", - name: "deepseek-v3.2", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 0.43 }, - limit: { context: 128000, output: 8192 }, - }, - "claude-opus-4-5-20251101-thinking": { - id: "claude-opus-4-5-20251101-thinking", - name: "claude-opus-4-5-20251101-thinking", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-11-25", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-haiku-4-5-20251001": { - id: "claude-haiku-4-5-20251001", - name: "claude-haiku-4-5-20251001", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-10-16", - last_updated: "2025-10-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5 }, - limit: { context: 200000, output: 64000 }, - }, - "gpt-5": { - id: "gpt-5", - name: "gpt-5", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-08", - last_updated: "2025-08-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, output: 128000 }, - }, - "deepseek-chat": { - id: "deepseek-chat", - name: "Deepseek-Chat", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-11-29", - last_updated: "2024-11-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 0.43 }, - limit: { context: 128000, output: 8192 }, - }, - "gpt-4.1-mini": { - id: "gpt-4.1-mini", - name: "gpt-4.1-mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.6 }, - limit: { context: 1000000, output: 32768 }, - }, - "gemini-2.5-flash-image": { - id: "gemini-2.5-flash-image", - name: "gemini-2.5-flash-image", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-01", - release_date: "2025-10-08", - last_updated: "2025-10-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 30 }, - limit: { context: 32768, output: 32768 }, - }, - "gemini-3-pro-image-preview": { - id: "gemini-3-pro-image-preview", - name: "gemini-3-pro-image-preview", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2025-11-20", - last_updated: "2025-11-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 120 }, - limit: { context: 32768, output: 64000 }, - }, - "glm-4.7": { - id: "glm-4.7", - name: "glm-4.7", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.286, output: 1.142 }, - limit: { context: 200000, output: 131072 }, - }, - "MiniMax-M1": { - id: "MiniMax-M1", - name: "MiniMax-M1", - family: "minimax", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-06-16", - last_updated: "2025-06-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.132, output: 1.254 }, - limit: { context: 1000000, output: 128000 }, - }, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "kimi-k2-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.575, output: 2.3 }, - limit: { context: 262144, output: 262144 }, - }, - "gpt-5-thinking": { - id: "gpt-5-thinking", - name: "gpt-5-thinking", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-08", - last_updated: "2025-08-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, output: 128000 }, - }, - "deepseek-v3.2-thinking": { - id: "deepseek-v3.2-thinking", - name: "DeepSeek-V3.2-Thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 0.43 }, - limit: { context: 128000, output: 128000 }, - }, - "chatgpt-4o-latest": { - id: "chatgpt-4o-latest", - name: "chatgpt-4o-latest", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-09", - release_date: "2024-08-08", - last_updated: "2024-08-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 15 }, - limit: { context: 128000, output: 16384 }, - }, - "qwen-plus": { - id: "qwen-plus", - name: "Qwen-Plus", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.12, output: 1.2 }, - limit: { context: 1000000, output: 32768 }, - }, - "MiniMax-M2.1": { - id: "MiniMax-M2.1", - name: "MiniMax-M2.1", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-12-19", - last_updated: "2025-12-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 1000000, output: 131072 }, - }, - "kimi-k2-thinking-turbo": { - id: "kimi-k2-thinking-turbo", - name: "kimi-k2-thinking-turbo", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.265, output: 9.119 }, - limit: { context: 262144, output: 262144 }, - }, - "gemini-3-pro-preview": { - id: "gemini-3-pro-preview", - name: "gemini-3-pro-preview", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12 }, - limit: { context: 1000000, output: 64000 }, - }, - "gemini-2.0-flash-lite": { - id: "gemini-2.0-flash-lite", - name: "gemini-2.0-flash-lite", - family: "gemini-flash-lite", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-11", - release_date: "2025-06-16", - last_updated: "2025-06-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 2000000, output: 8192 }, - }, - "doubao-seed-code-preview-251028": { - id: "doubao-seed-code-preview-251028", - name: "doubao-seed-code-preview-251028", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-11-11", - last_updated: "2025-11-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.17, output: 1.14 }, - limit: { context: 256000, output: 32000 }, - }, - "qwen3-30b-a3b": { - id: "qwen3-30b-a3b", - name: "Qwen3-30B-A3B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-29", - last_updated: "2025-04-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.11, output: 1.08 }, - limit: { context: 128000, output: 8192 }, - }, - "grok-4-fast-non-reasoning": { - id: "grok-4-fast-non-reasoning", - name: "grok-4-fast-non-reasoning", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-09-23", - last_updated: "2025-09-23", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5 }, - limit: { context: 2000000, output: 30000 }, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - name: "gpt-5-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-08", - last_updated: "2025-08-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2 }, - limit: { context: 400000, output: 128000 }, - }, - "glm-4.5v": { - id: "glm-4.5v", - name: "GLM-4.5V", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-29", - last_updated: "2025-07-29", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 0.86 }, - limit: { context: 64000, output: 16384 }, - }, - "qwen-flash": { - id: "qwen-flash", - name: "Qwen-Flash", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.022, output: 0.22 }, - limit: { context: 1000000, output: 32768 }, - }, - "glm-4.6v": { - id: "glm-4.6v", - name: "GLM-4.6V", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.145, output: 0.43 }, - limit: { context: 128000, output: 32768 }, - }, - "gpt-5.1-chat-latest": { - id: "gpt-5.1-chat-latest", - name: "gpt-5.1-chat-latest", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 128000, output: 16384 }, - }, - "claude-opus-4-1-20250805": { - id: "claude-opus-4-1-20250805", - name: "claude-opus-4-1-20250805", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75 }, - limit: { context: 200000, output: 32000 }, - }, - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - name: "gemini-2.5-pro", - family: "gemini-pro", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 1000000, output: 65536 }, - }, - "gpt-4o": { - id: "gpt-4o", - name: "gpt-4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 128000, output: 16384 }, - }, - "grok-4.1": { - id: "grok-4.1", - name: "grok-4.1", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 10 }, - limit: { context: 200000, output: 64000 }, - }, - }, - }, - "github-copilot": { - id: "github-copilot", - env: ["GITHUB_TOKEN"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.githubcopilot.com", - name: "GitHub Copilot", - doc: "https://docs.github.com/en/copilot", - models: { - "gpt-5.3-codex": { - id: "gpt-5.3-codex", - name: "GPT-5.3-Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-02-24", - last_updated: "2026-02-24", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "gpt-5.1-codex-max": { - id: "gpt-5.1-codex-max", - name: "GPT-5.1-Codex-max", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-12-04", - last_updated: "2025-12-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 128000 }, - }, - "gpt-5.2-codex": { - id: "gpt-5.2-codex", - name: "GPT-5.2-Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "grok-code-fast-1": { - id: "grok-code-fast-1", - name: "Grok Code Fast 1", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-08-27", - last_updated: "2025-08-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 64000 }, - }, - "gpt-5.1": { - id: "gpt-5.1", - name: "GPT-5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 64000 }, - }, - "claude-sonnet-4.6": { - id: "claude-sonnet-4.6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32000 }, - }, - "gemini-3-flash-preview": { - id: "gemini-3-flash-preview", - name: "Gemini 3 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 64000 }, - }, - "claude-haiku-4.5": { - id: "claude-haiku-4.5", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32000 }, - }, - "gpt-5.1-codex-mini": { - id: "gpt-5.1-codex-mini", - name: "GPT-5.1-Codex-mini", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 128000 }, - }, - "gpt-5.2": { - id: "gpt-5.2", - name: "GPT-5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 264000, input: 128000, output: 64000 }, - }, - "gpt-4.1": { - id: "gpt-4.1", - name: "GPT-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 64000, output: 16384 }, - }, - "claude-opus-4.5": { - id: "claude-opus-4.5", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-08-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32000 }, - }, - "gemini-3.1-pro-preview": { - id: "gemini-3.1-pro-preview", - name: "Gemini 3.1 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-19", - last_updated: "2026-02-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 64000 }, - }, - "gpt-5": { - id: "gpt-5", - name: "GPT-5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 128000 }, - }, - "gpt-5.4": { - id: "gpt-5.4", - name: "GPT-5.4", - family: "gpt", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "gpt-5.1-codex": { - id: "gpt-5.1-codex", - name: "GPT-5.1-Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 128000 }, - }, - "claude-sonnet-4": { - id: "claude-sonnet-4", - name: "Claude Sonnet 4", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16000 }, - }, - "gemini-3-pro-preview": { - id: "gemini-3-pro-preview", - name: "Gemini 3 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 64000 }, - }, - "claude-sonnet-4.5": { - id: "claude-sonnet-4.5", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32000 }, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - name: "GPT-5-mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2025-08-13", - last_updated: "2025-08-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 64000 }, - }, - "claude-opus-4.6": { - id: "claude-opus-4.6", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 64000 }, - }, - "claude-opus-41": { - id: "claude-opus-41", - name: "Claude Opus 4.1", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 80000, output: 16000 }, - }, - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-pro", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 64000 }, - }, - "gpt-4o": { - id: "gpt-4o", - name: "GPT-4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 64000, output: 16384 }, - }, - }, - }, - moonshotai: { - id: "moonshotai", - env: ["MOONSHOT_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.moonshot.ai/v1", - name: "Moonshot AI", - doc: "https://platform.moonshot.ai/docs/api/chat", - models: { - "kimi-k2-0905-preview": { - id: "kimi-k2-0905-preview", - name: "Kimi K2 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5, cache_read: 0.15 }, - limit: { context: 262144, output: 262144 }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: false, - knowledge: "2025-01", - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3, cache_read: 0.1 }, - limit: { context: 262144, output: 262144 }, - }, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-08", - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5, cache_read: 0.15 }, - limit: { context: 262144, output: 262144 }, - }, - "kimi-k2-turbo-preview": { - id: "kimi-k2-turbo-preview", - name: "Kimi K2 Turbo", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.4, output: 10, cache_read: 0.6 }, - limit: { context: 262144, output: 262144 }, - }, - "kimi-k2-thinking-turbo": { - id: "kimi-k2-thinking-turbo", - name: "Kimi K2 Thinking Turbo", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-08", - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.15, output: 8, cache_read: 0.15 }, - limit: { context: 262144, output: 262144 }, - }, - "kimi-k2-0711-preview": { - id: "kimi-k2-0711-preview", - name: "Kimi K2 0711", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-14", - last_updated: "2025-07-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5, cache_read: 0.15 }, - limit: { context: 131072, output: 16384 }, - }, - }, - }, - "google-vertex": { - id: "google-vertex", - env: ["GOOGLE_VERTEX_PROJECT", "GOOGLE_VERTEX_LOCATION", "GOOGLE_APPLICATION_CREDENTIALS"], - npm: "@ai-sdk/google-vertex", - name: "Vertex", - doc: "https://cloud.google.com/vertex-ai/generative-ai/docs/models", - models: { - "gemini-embedding-001": { - id: "gemini-embedding-001", - name: "Gemini Embedding 001", - family: "gemini", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2025-05", - release_date: "2025-05-20", - last_updated: "2025-05-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0 }, - limit: { context: 2048, output: 3072 }, - }, - "gemini-2.5-flash-lite-preview-09-2025": { - id: "gemini-2.5-flash-lite-preview-09-2025", - name: "Gemini 2.5 Flash Lite Preview 09-25", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-3.1-pro-preview-customtools": { - id: "gemini-3.1-pro-preview-customtools", - name: "Gemini 3.1 Pro Preview Custom Tools", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-19", - last_updated: "2026-02-19", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, context_over_200k: { input: 4, output: 18, cache_read: 0.4 } }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-pro-preview-06-05": { - id: "gemini-2.5-pro-preview-06-05", - name: "Gemini 2.5 Pro Preview 06-05", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-05", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-flash-preview-04-17": { - id: "gemini-2.5-flash-preview-04-17", - name: "Gemini 2.5 Flash Preview 04-17", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-04-17", - last_updated: "2025-04-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.0375 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-flash-preview-09-2025": { - id: "gemini-2.5-flash-preview-09-2025", - name: "Gemini 2.5 Flash Preview 09-25", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.075, cache_write: 0.383 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-pro-preview-05-06": { - id: "gemini-2.5-pro-preview-05-06", - name: "Gemini 2.5 Pro Preview 05-06", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-05-06", - last_updated: "2025-05-06", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-flash-preview-05-20": { - id: "gemini-2.5-flash-preview-05-20", - name: "Gemini 2.5 Flash Preview 05-20", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-05-20", - last_updated: "2025-05-20", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.0375 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-flash": { - id: "gemini-2.5-flash", - name: "Gemini 2.5 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.075, cache_write: 0.383 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-3-flash-preview": { - id: "gemini-3-flash-preview", - name: "Gemini 3 Flash Preview", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 0.5, - output: 3, - cache_read: 0.05, - context_over_200k: { input: 0.5, output: 3, cache_read: 0.05 }, - }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-flash-lite": { - id: "gemini-2.5-flash-lite", - name: "Gemini 2.5 Flash Lite", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-3.1-pro-preview": { - id: "gemini-3.1-pro-preview", - name: "Gemini 3.1 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-19", - last_updated: "2026-02-19", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, context_over_200k: { input: 4, output: 18, cache_read: 0.4 } }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-flash-latest": { - id: "gemini-flash-latest", - name: "Gemini Flash Latest", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.075, cache_write: 0.383 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-flash-lite-preview-06-17": { - id: "gemini-2.5-flash-lite-preview-06-17", - name: "Gemini 2.5 Flash Lite Preview 06-17", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 65536, output: 65536 }, - }, - "gemini-3-pro-preview": { - id: "gemini-3-pro-preview", - name: "Gemini 3 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, context_over_200k: { input: 4, output: 18, cache_read: 0.4 } }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.0-flash-lite": { - id: "gemini-2.0-flash-lite", - name: "Gemini 2.0 Flash Lite", - family: "gemini-flash-lite", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 1048576, output: 8192 }, - }, - "gemini-flash-lite-latest": { - id: "gemini-flash-lite-latest", - name: "Gemini Flash-Lite Latest", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.0-flash": { - id: "gemini-2.0-flash", - name: "Gemini 2.0 Flash", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.025 }, - limit: { context: 1048576, output: 8192 }, - }, - "zai-org/glm-5-maas": { - id: "zai-org/glm-5-maas", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2, cache_read: 0.1 }, - limit: { context: 202752, output: 131072 }, - provider: { - npm: "@ai-sdk/openai-compatible", - api: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}/endpoints/openapi", - }, - }, - "zai-org/glm-4.7-maas": { - id: "zai-org/glm-4.7-maas", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-01-06", - last_updated: "2026-01-06", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 200000, output: 128000 }, - provider: { - npm: "@ai-sdk/openai-compatible", - api: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}/endpoints/openapi", - }, - }, - "deepseek-ai/deepseek-v3.1-maas": { - id: "deepseek-ai/deepseek-v3.1-maas", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-28", - last_updated: "2025-08-28", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 1.7 }, - limit: { context: 163840, output: 32768 }, - provider: { - npm: "@ai-sdk/openai-compatible", - api: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}/endpoints/openapi", - }, - }, - "qwen/qwen3-235b-a22b-instruct-2507-maas": { - id: "qwen/qwen3-235b-a22b-instruct-2507-maas", - name: "Qwen3 235B A22B Instruct", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-13", - last_updated: "2025-08-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.22, output: 0.88 }, - limit: { context: 262144, output: 16384 }, - provider: { - npm: "@ai-sdk/openai-compatible", - api: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}/endpoints/openapi", - }, - }, - "meta/llama-4-maverick-17b-128e-instruct-maas": { - id: "meta/llama-4-maverick-17b-128e-instruct-maas", - name: "Llama 4 Maverick 17B 128E Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-29", - last_updated: "2025-04-29", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.35, output: 1.15 }, - limit: { context: 524288, output: 8192 }, - provider: { - npm: "@ai-sdk/openai-compatible", - api: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}/endpoints/openapi", - }, - }, - "meta/llama-3.3-70b-instruct-maas": { - id: "meta/llama-3.3-70b-instruct-maas", - name: "Llama 3.3 70B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-12", - release_date: "2025-04-29", - last_updated: "2025-04-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.72, output: 0.72 }, - limit: { context: 128000, output: 8192 }, - provider: { - npm: "@ai-sdk/openai-compatible", - api: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}/endpoints/openapi", - }, - }, - "openai/gpt-oss-20b-maas": { - id: "openai/gpt-oss-20b-maas", - name: "GPT OSS 20B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.25 }, - limit: { context: 131072, output: 32768 }, - }, - "openai/gpt-oss-120b-maas": { - id: "openai/gpt-oss-120b-maas", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.36 }, - limit: { context: 131072, output: 32768 }, - }, - }, - }, - "privatemode-ai": { - id: "privatemode-ai", - env: ["PRIVATEMODE_API_KEY", "PRIVATEMODE_ENDPOINT"], - npm: "@ai-sdk/openai-compatible", - api: "http://localhost:8080/v1", - name: "Privatemode AI", - doc: "https://docs.privatemode.ai/api/overview", - models: { - "gemma-3-27b": { - id: "gemma-3-27b", - name: "Gemma 3 27B", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-03-12", - last_updated: "2025-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 8192 }, - }, - "gpt-oss-120b": { - id: "gpt-oss-120b", - name: "gpt-oss-120b", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-08-04", - last_updated: "2025-08-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 128000 }, - }, - "whisper-large-v3": { - id: "whisper-large-v3", - name: "Whisper large-v3", - family: "whisper", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - knowledge: "2023-09", - release_date: "2023-09-01", - last_updated: "2023-09-01", - modalities: { input: ["audio"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 0, output: 4096 }, - }, - "qwen3-embedding-4b": { - id: "qwen3-embedding-4b", - name: "Qwen3-Embedding 4B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - knowledge: "2025-06", - release_date: "2025-06-06", - last_updated: "2025-06-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32000, output: 2560 }, - }, - "qwen3-coder-30b-a3b": { - id: "qwen3-coder-30b-a3b", - name: "Qwen3-Coder 30B-A3B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - }, - }, - google: { - id: "google", - env: ["GOOGLE_GENERATIVE_AI_API_KEY", "GEMINI_API_KEY"], - npm: "@ai-sdk/google", - name: "Google", - doc: "https://ai.google.dev/gemini-api/docs/pricing", - models: { - "gemini-embedding-001": { - id: "gemini-embedding-001", - name: "Gemini Embedding 001", - family: "gemini", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2025-05", - release_date: "2025-05-20", - last_updated: "2025-05-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0 }, - limit: { context: 2048, output: 3072 }, - }, - "gemini-2.5-flash-lite-preview-09-2025": { - id: "gemini-2.5-flash-lite-preview-09-2025", - name: "Gemini 2.5 Flash Lite Preview 09-25", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-3.1-pro-preview-customtools": { - id: "gemini-3.1-pro-preview-customtools", - name: "Gemini 3.1 Pro Preview Custom Tools", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-19", - last_updated: "2026-02-19", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, context_over_200k: { input: 4, output: 18, cache_read: 0.4 } }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-pro-preview-06-05": { - id: "gemini-2.5-pro-preview-06-05", - name: "Gemini 2.5 Pro Preview 06-05", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-05", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-flash-preview-04-17": { - id: "gemini-2.5-flash-preview-04-17", - name: "Gemini 2.5 Flash Preview 04-17", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-04-17", - last_updated: "2025-04-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.0375 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-flash-preview-09-2025": { - id: "gemini-2.5-flash-preview-09-2025", - name: "Gemini 2.5 Flash Preview 09-25", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.075, input_audio: 1 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-pro-preview-05-06": { - id: "gemini-2.5-pro-preview-05-06", - name: "Gemini 2.5 Pro Preview 05-06", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-05-06", - last_updated: "2025-05-06", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-flash-preview-05-20": { - id: "gemini-2.5-flash-preview-05-20", - name: "Gemini 2.5 Flash Preview 05-20", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-05-20", - last_updated: "2025-05-20", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.0375 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-flash": { - id: "gemini-2.5-flash", - name: "Gemini 2.5 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.075, input_audio: 1 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-3.1-flash-image-preview": { - id: "gemini-3.1-flash-image-preview", - name: "Gemini 3.1 Flash Image (Preview)", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-26", - last_updated: "2026-02-26", - modalities: { input: ["text", "image", "pdf"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 0.25, output: 60 }, - limit: { context: 131072, output: 32768 }, - }, - "gemini-3.1-flash-lite-preview": { - id: "gemini-3.1-flash-lite-preview", - name: "Gemini 3.1 Flash Lite Preview", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-03-03", - last_updated: "2026-03-03", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1.5, cache_read: 0.025, cache_write: 1 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-live-2.5-flash": { - id: "gemini-live-2.5-flash", - name: "Gemini Live 2.5 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-01", - last_updated: "2025-09-01", - modalities: { input: ["text", "image", "audio", "video"], output: ["text", "audio"] }, - open_weights: false, - cost: { input: 0.5, output: 2, input_audio: 3, output_audio: 12 }, - limit: { context: 128000, output: 8000 }, - }, - "gemini-3-flash-preview": { - id: "gemini-3-flash-preview", - name: "Gemini 3 Flash Preview", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 0.5, - output: 3, - cache_read: 0.05, - context_over_200k: { input: 0.5, output: 3, cache_read: 0.05 }, - }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-live-2.5-flash-preview-native-audio": { - id: "gemini-live-2.5-flash-preview-native-audio", - name: "Gemini Live 2.5 Flash Preview Native Audio", - family: "gemini-flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-09-18", - modalities: { input: ["text", "audio", "video"], output: ["text", "audio"] }, - open_weights: false, - cost: { input: 0.5, output: 2, input_audio: 3, output_audio: 12 }, - limit: { context: 131072, output: 65536 }, - }, - "gemini-2.5-flash-lite": { - id: "gemini-2.5-flash-lite", - name: "Gemini 2.5 Flash Lite", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-flash-preview-tts": { - id: "gemini-2.5-flash-preview-tts", - name: "Gemini 2.5 Flash Preview TTS", - family: "gemini-flash", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2025-01", - release_date: "2025-05-01", - last_updated: "2025-05-01", - modalities: { input: ["text"], output: ["audio"] }, - open_weights: false, - cost: { input: 0.5, output: 10 }, - limit: { context: 8000, output: 16000 }, - }, - "gemini-3.1-pro-preview": { - id: "gemini-3.1-pro-preview", - name: "Gemini 3.1 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-19", - last_updated: "2026-02-19", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, context_over_200k: { input: 4, output: 18, cache_read: 0.4 } }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-flash-latest": { - id: "gemini-flash-latest", - name: "Gemini Flash Latest", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.075, input_audio: 1 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-flash-lite-preview-06-17": { - id: "gemini-2.5-flash-lite-preview-06-17", - name: "Gemini 2.5 Flash Lite Preview 06-17", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025, input_audio: 0.3 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-flash-image": { - id: "gemini-2.5-flash-image", - name: "Gemini 2.5 Flash Image", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2025-08-26", - last_updated: "2025-08-26", - modalities: { input: ["text", "image"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 0.3, output: 30, cache_read: 0.075 }, - limit: { context: 32768, output: 32768 }, - }, - "gemini-2.5-pro-preview-tts": { - id: "gemini-2.5-pro-preview-tts", - name: "Gemini 2.5 Pro Preview TTS", - family: "gemini-flash", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2025-01", - release_date: "2025-05-01", - last_updated: "2025-05-01", - modalities: { input: ["text"], output: ["audio"] }, - open_weights: false, - cost: { input: 1, output: 20 }, - limit: { context: 8000, output: 16000 }, - }, - "gemini-2.5-flash-image-preview": { - id: "gemini-2.5-flash-image-preview", - name: "Gemini 2.5 Flash Image (Preview)", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-06", - release_date: "2025-08-26", - last_updated: "2025-08-26", - modalities: { input: ["text", "image"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 0.3, output: 30, cache_read: 0.075 }, - limit: { context: 32768, output: 32768 }, - }, - "gemini-1.5-flash-8b": { - id: "gemini-1.5-flash-8b", - name: "Gemini 1.5 Flash-8B", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-10-03", - last_updated: "2024-10-03", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.0375, output: 0.15, cache_read: 0.01 }, - limit: { context: 1000000, output: 8192 }, - }, - "gemini-3-pro-preview": { - id: "gemini-3-pro-preview", - name: "Gemini 3 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, context_over_200k: { input: 4, output: 18, cache_read: 0.4 } }, - limit: { context: 1000000, output: 64000 }, - }, - "gemini-2.0-flash-lite": { - id: "gemini-2.0-flash-lite", - name: "Gemini 2.0 Flash Lite", - family: "gemini-flash-lite", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-06", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 1048576, output: 8192 }, - }, - "gemini-1.5-flash": { - id: "gemini-1.5-flash", - name: "Gemini 1.5 Flash", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-05-14", - last_updated: "2024-05-14", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.075, output: 0.3, cache_read: 0.01875 }, - limit: { context: 1000000, output: 8192 }, - }, - "gemini-flash-lite-latest": { - id: "gemini-flash-lite-latest", - name: "Gemini Flash-Lite Latest", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31 }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-2.0-flash": { - id: "gemini-2.0-flash", - name: "Gemini 2.0 Flash", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-06", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 1048576, output: 8192 }, - }, - "gemini-1.5-pro": { - id: "gemini-1.5-pro", - name: "Gemini 1.5 Pro", - family: "gemini-pro", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-02-15", - last_updated: "2024-02-15", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 5, cache_read: 0.3125 }, - limit: { context: 1000000, output: 8192 }, - }, - }, - }, - vivgrid: { - id: "vivgrid", - env: ["VIVGRID_API_KEY"], - npm: "@ai-sdk/openai", - api: "https://api.vivgrid.com/v1", - name: "Vivgrid", - doc: "https://docs.vivgrid.com/models", - models: { - "glm-5": { - id: "glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2, cache_read: 0.2 }, - limit: { context: 202752, output: 131000 }, - provider: { npm: "@ai-sdk/openai-compatible" }, - }, - "gpt-5.1-codex-max": { - id: "gpt-5.1-codex-max", - name: "GPT-5.1 Codex Max", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-5.2-codex": { - id: "gpt-5.2-codex", - name: "GPT-5.2 Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-01-14", - last_updated: "2026-01-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "gemini-3-flash-preview": { - id: "gemini-3-flash-preview", - name: "Gemini 3 Flash Preview", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 0.5, - output: 3, - cache_read: 0.05, - context_over_200k: { input: 0.5, output: 3, cache_read: 0.05 }, - }, - limit: { context: 1048576, output: 65536 }, - provider: { npm: "@ai-sdk/openai-compatible" }, - }, - "deepseek-v3.2": { - id: "deepseek-v3.2", - name: "DeepSeek-V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 0.42 }, - limit: { context: 128000, output: 128000 }, - provider: { npm: "@ai-sdk/openai-compatible" }, - }, - "gpt-5.1-codex": { - id: "gpt-5.1-codex", - name: "GPT-5.1 Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "gemini-3-pro-preview": { - id: "gemini-3-pro-preview", - name: "Gemini 3 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, context_over_200k: { input: 4, output: 18, cache_read: 0.4 } }, - limit: { context: 1048576, output: 65536 }, - provider: { npm: "@ai-sdk/openai-compatible" }, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - name: "GPT-5 Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.03 }, - limit: { context: 272000, output: 128000 }, - provider: { npm: "@ai-sdk/openai-compatible" }, - }, - }, - }, - "moonshotai-cn": { - id: "moonshotai-cn", - env: ["MOONSHOT_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.moonshot.cn/v1", - name: "Moonshot AI (China)", - doc: "https://platform.moonshot.cn/docs/api/chat", - models: { - "kimi-k2-0711-preview": { - id: "kimi-k2-0711-preview", - name: "Kimi K2 0711", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-14", - last_updated: "2025-07-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5, cache_read: 0.15 }, - limit: { context: 131072, output: 16384 }, - }, - "kimi-k2-thinking-turbo": { - id: "kimi-k2-thinking-turbo", - name: "Kimi K2 Thinking Turbo", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-08", - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.15, output: 8, cache_read: 0.15 }, - limit: { context: 262144, output: 262144 }, - }, - "kimi-k2-turbo-preview": { - id: "kimi-k2-turbo-preview", - name: "Kimi K2 Turbo", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.4, output: 10, cache_read: 0.6 }, - limit: { context: 262144, output: 262144 }, - }, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-08", - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5, cache_read: 0.15 }, - limit: { context: 262144, output: 262144 }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: false, - knowledge: "2025-01", - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3, cache_read: 0.1 }, - limit: { context: 262144, output: 262144 }, - }, - "kimi-k2-0905-preview": { - id: "kimi-k2-0905-preview", - name: "Kimi K2 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5, cache_read: 0.15 }, - limit: { context: 262144, output: 262144 }, - }, - }, - }, - "sap-ai-core": { - id: "sap-ai-core", - env: ["AICORE_SERVICE_KEY"], - npm: "@jerome-benoit/sap-ai-provider-v2", - name: "SAP AI Core", - doc: "https://help.sap.com/docs/sap-ai-core", - models: { - "anthropic--claude-4.5-opus": { - id: "anthropic--claude-4.5-opus", - name: "anthropic--claude-4.5-opus", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04-30", - release_date: "2025-11-24", - last_updated: "2025-11-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic--claude-4-sonnet": { - id: "anthropic--claude-4-sonnet", - name: "anthropic--claude-4-sonnet", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic--claude-4.5-sonnet": { - id: "anthropic--claude-4.5-sonnet", - name: "anthropic--claude-4.5-sonnet", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "gemini-2.5-flash": { - id: "gemini-2.5-flash", - name: "gemini-2.5-flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-25", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.03, input_audio: 1 }, - limit: { context: 1048576, output: 65536 }, - }, - "anthropic--claude-3-sonnet": { - id: "anthropic--claude-3-sonnet", - name: "anthropic--claude-3-sonnet", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-08-31", - release_date: "2024-03-04", - last_updated: "2024-03-04", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 4096 }, - }, - "anthropic--claude-3.7-sonnet": { - id: "anthropic--claude-3.7-sonnet", - name: "anthropic--claude-3.7-sonnet", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10-31", - release_date: "2025-02-24", - last_updated: "2025-02-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - sonar: { - id: "sonar", - name: "sonar", - family: "sonar", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-09-01", - release_date: "2024-01-01", - last_updated: "2025-09-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 1 }, - limit: { context: 128000, output: 4096 }, - }, - "anthropic--claude-3.5-sonnet": { - id: "anthropic--claude-3.5-sonnet", - name: "anthropic--claude-3.5-sonnet", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04-30", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 8192 }, - }, - "sonar-deep-research": { - id: "sonar-deep-research", - name: "sonar-deep-research", - family: "sonar-deep-research", - attachment: false, - reasoning: true, - tool_call: false, - temperature: false, - knowledge: "2025-01", - release_date: "2025-02-01", - last_updated: "2025-09-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, reasoning: 3 }, - limit: { context: 128000, output: 32768 }, - }, - "anthropic--claude-4.6-sonnet": { - id: "anthropic--claude-4.6-sonnet", - name: "anthropic--claude-4.6-sonnet", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 200000, output: 64000 }, - }, - "gemini-2.5-flash-lite": { - id: "gemini-2.5-flash-lite", - name: "gemini-2.5-flash-lite", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 1048576, output: 65536 }, - }, - "gpt-4.1": { - id: "gpt-4.1", - name: "gpt-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 1047576, output: 32768 }, - }, - "anthropic--claude-4.5-haiku": { - id: "anthropic--claude-4.5-haiku", - name: "anthropic--claude-4.5-haiku", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-01", - last_updated: "2025-10-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "gpt-5": { - id: "gpt-5", - name: "gpt-5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-4.1-mini": { - id: "gpt-4.1-mini", - name: "gpt-4.1-mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.6, cache_read: 0.1 }, - limit: { context: 1047576, output: 32768 }, - }, - "anthropic--claude-3-opus": { - id: "anthropic--claude-3-opus", - name: "anthropic--claude-3-opus", - family: "claude-opus", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-08-31", - release_date: "2024-02-29", - last_updated: "2024-02-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 4096 }, - }, - "sonar-pro": { - id: "sonar-pro", - name: "sonar-pro", - family: "sonar-pro", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-09-01", - release_date: "2024-01-01", - last_updated: "2025-09-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, output: 8192 }, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - name: "gpt-5-mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.025 }, - limit: { context: 400000, output: 128000 }, - }, - "anthropic--claude-3-haiku": { - id: "anthropic--claude-3-haiku", - name: "anthropic--claude-3-haiku", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-08-31", - release_date: "2024-03-13", - last_updated: "2024-03-13", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1.25, cache_read: 0.03, cache_write: 0.3 }, - limit: { context: 200000, output: 4096 }, - }, - "anthropic--claude-4.6-opus": { - id: "anthropic--claude-4.6-opus", - name: "anthropic--claude-4.6-opus", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 5, - output: 25, - cache_read: 0.5, - cache_write: 6.25, - context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 }, - }, - limit: { context: 200000, output: 128000 }, - }, - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - name: "gemini-2.5-pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-25", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 1048576, output: 65536 }, - }, - "gpt-5-nano": { - id: "gpt-5-nano", - name: "gpt-5-nano", - family: "gpt-nano", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4, cache_read: 0.01 }, - limit: { context: 400000, output: 128000 }, - }, - "anthropic--claude-4-opus": { - id: "anthropic--claude-4-opus", - name: "anthropic--claude-4-opus", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - }, - }, - zhipuai: { - id: "zhipuai", - env: ["ZHIPU_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://open.bigmodel.cn/api/paas/v4", - name: "Zhipu AI", - doc: "https://docs.z.ai/guides/overview/pricing", - models: { - "glm-5": { - id: "glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2, cache_read: 0.2, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "glm-4.6v": { - id: "glm-4.6v", - name: "GLM-4.6V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 128000, output: 32768 }, - }, - "glm-4.5v": { - id: "glm-4.5v", - name: "GLM-4.5V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-08-11", - last_updated: "2025-08-11", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 1.8 }, - limit: { context: 64000, output: 16384 }, - }, - "glm-4.7": { - id: "glm-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.11, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "glm-4.6": { - id: "glm-4.6", - name: "GLM-4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.11, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "glm-4.7-flash": { - id: "glm-4.7-flash", - name: "GLM-4.7-Flash", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 200000, output: 131072 }, - }, - "glm-4.5-flash": { - id: "glm-4.5-flash", - name: "GLM-4.5-Flash", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 131072, output: 98304 }, - }, - "glm-4.5": { - id: "glm-4.5", - name: "GLM-4.5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.11, cache_write: 0 }, - limit: { context: 131072, output: 98304 }, - }, - "glm-4.5-air": { - id: "glm-4.5-air", - name: "GLM-4.5-Air", - family: "glm-air", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 1.1, cache_read: 0.03, cache_write: 0 }, - limit: { context: 131072, output: 98304 }, - }, - }, - }, - venice: { - id: "venice", - env: ["VENICE_API_KEY"], - npm: "venice-ai-sdk-provider", - name: "Venice AI", - doc: "https://docs.venice.ai", - models: { - "qwen3-235b-a22b-instruct-2507": { - id: "qwen3-235b-a22b-instruct-2507", - name: "Qwen 3 235B A22B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-04-29", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.75 }, - limit: { context: 128000, output: 16384 }, - }, - "google-gemma-3-27b-it": { - id: "google-gemma-3-27b-it", - name: "Google Gemma 3 27B Instruct", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-11-04", - last_updated: "2026-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.12, output: 0.2 }, - limit: { context: 198000, output: 16384 }, - }, - "openai-gpt-4o-2024-11-20": { - id: "openai-gpt-4o-2024-11-20", - name: "GPT-4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-28", - last_updated: "2026-03-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3.125, output: 12.5 }, - limit: { context: 128000, output: 16384 }, - }, - "claude-opus-45": { - id: "claude-opus-45", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-12-06", - last_updated: "2026-01-28", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 6, output: 30, cache_read: 0.6, cache_write: 7.5 }, - limit: { context: 198000, output: 49500 }, - }, - "qwen3-coder-480b-a35b-instruct": { - id: "qwen3-coder-480b-a35b-instruct", - name: "Qwen 3 Coder 480b", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-04-29", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.75, output: 3 }, - limit: { context: 256000, output: 65536 }, - }, - "claude-opus-4-6": { - id: "claude-opus-4-6", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-05", - last_updated: "2026-02-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { - input: 6, - output: 30, - cache_read: 0.6, - cache_write: 7.5, - context_over_200k: { input: 11, output: 41.25, cache_read: 1.1, cache_write: 13.75 }, - }, - limit: { context: 1000000, output: 128000 }, - }, - "grok-code-fast-1": { - id: "grok-code-fast-1", - name: "Grok Code Fast 1", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-01", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1.87, cache_read: 0.03 }, - limit: { context: 256000, output: 10000 }, - }, - "zai-org-glm-5": { - id: "zai-org-glm-5", - name: "GLM 5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2, cache_read: 0.2 }, - limit: { context: 198000, output: 32000 }, - }, - "zai-org-glm-4.7": { - id: "zai-org-glm-4.7", - name: "GLM 4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-24", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.65, cache_read: 0.11 }, - limit: { context: 198000, output: 16384 }, - }, - "claude-sonnet-4-6": { - id: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-17", - last_updated: "2026-02-28", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { - input: 3.6, - output: 18, - cache_read: 0.36, - cache_write: 4.5, - context_over_200k: { input: 7.2, output: 27, cache_read: 0.72, cache_write: 9 }, - }, - limit: { context: 1000000, output: 64000 }, - }, - "openai-gpt-53-codex": { - id: "openai-gpt-53-codex", - name: "GPT-5.3 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-24", - last_updated: "2026-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.19, output: 17.5, cache_read: 0.219 }, - limit: { context: 400000, output: 128000 }, - }, - "kimi-k2-5": { - id: "kimi-k2-5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2026-01-27", - last_updated: "2026-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.75, output: 3.75, cache_read: 0.125 }, - limit: { context: 256000, output: 65536 }, - }, - "mistral-31-24b": { - id: "mistral-31-24b", - name: "Venice Medium", - family: "mistral", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-10", - release_date: "2025-03-18", - last_updated: "2026-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 2 }, - limit: { context: 128000, output: 4096 }, - }, - "grok-4-20-multi-agent-beta": { - id: "grok-4-20-multi-agent-beta", - name: "Grok 4.20 Multi-Agent Beta", - family: "grok-beta", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2026-03-12", - last_updated: "2026-03-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { - input: 2.5, - output: 7.5, - cache_read: 0.25, - context_over_200k: { input: 5, output: 15, cache_read: 0.5 }, - }, - limit: { context: 2000000, output: 128000 }, - }, - "openai-gpt-54-pro": { - id: "openai-gpt-54-pro", - name: "GPT-5.4 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-03-05", - last_updated: "2026-03-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 37.5, output: 225, context_over_200k: { input: 75, output: 337.5 } }, - limit: { context: 1000000, output: 128000 }, - }, - "qwen3-4b": { - id: "qwen3-4b", - name: "Venice Small", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-04-29", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.15 }, - limit: { context: 32000, output: 4096 }, - }, - "gemini-3-flash-preview": { - id: "gemini-3-flash-preview", - name: "Gemini 3 Flash Preview", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-19", - last_updated: "2026-03-12", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7, output: 3.75, cache_read: 0.07 }, - limit: { context: 256000, output: 65536 }, - }, - "grok-4-20-beta": { - id: "grok-4-20-beta", - name: "Grok 4.20 Beta", - family: "grok-beta", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-03-12", - last_updated: "2026-03-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { - input: 2.5, - output: 7.5, - cache_read: 0.25, - context_over_200k: { input: 5, output: 15, cache_read: 0.5 }, - }, - limit: { context: 2000000, output: 128000 }, - }, - "olafangensan-glm-4.7-flash-heretic": { - id: "olafangensan-glm-4.7-flash-heretic", - name: "GLM 4.7 Flash Heretic", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-04", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.14, output: 0.8 }, - limit: { context: 200000, output: 24000 }, - }, - "minimax-m25": { - id: "minimax-m25", - name: "MiniMax M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 1.6, cache_read: 0.04 }, - limit: { context: 198000, output: 32768 }, - }, - "zai-org-glm-4.7-flash": { - id: "zai-org-glm-4.7-flash", - name: "GLM 4.7 Flash", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01-29", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.125, output: 0.5 }, - limit: { context: 128000, output: 16384 }, - }, - "qwen3-coder-480b-a35b-instruct-turbo": { - id: "qwen3-coder-480b-a35b-instruct-turbo", - name: "Qwen 3 Coder 480B Turbo", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01-27", - last_updated: "2026-02-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.35, output: 1.5, cache_read: 0.04 }, - limit: { context: 256000, output: 65536 }, - }, - "openai-gpt-oss-120b": { - id: "openai-gpt-oss-120b", - name: "OpenAI GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-11-06", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.3 }, - limit: { context: 128000, output: 16384 }, - }, - "grok-41-fast": { - id: "grok-41-fast", - name: "Grok 4.1 Fast", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-12-01", - last_updated: "2026-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.625, cache_read: 0.0625 }, - limit: { context: 1000000, output: 30000 }, - }, - "openai-gpt-52": { - id: "openai-gpt-52", - name: "GPT-5.2", - family: "gpt", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-08-31", - release_date: "2025-12-13", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.19, output: 17.5, cache_read: 0.219 }, - limit: { context: 256000, output: 65536 }, - }, - "openai-gpt-54": { - id: "openai-gpt-54", - name: "GPT-5.4", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-03-05", - last_updated: "2026-03-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3.13, output: 18.8, cache_read: 0.313 }, - limit: { context: 1000000, output: 131072 }, - }, - "deepseek-v3.2": { - id: "deepseek-v3.2", - name: "DeepSeek V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-10", - release_date: "2025-12-04", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 1, cache_read: 0.2 }, - limit: { context: 160000, output: 32768 }, - }, - "gemini-3-1-pro-preview": { - id: "gemini-3-1-pro-preview", - name: "Gemini 3.1 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-19", - last_updated: "2026-03-12", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { - input: 2.5, - output: 15, - cache_read: 0.5, - cache_write: 0.5, - context_over_200k: { input: 5, output: 22.5, cache_read: 0.5 }, - }, - limit: { context: 1000000, output: 32768 }, - }, - "openai-gpt-4o-mini-2024-07-18": { - id: "openai-gpt-4o-mini-2024-07-18", - name: "GPT-4o Mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-28", - last_updated: "2026-03-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1875, output: 0.75, cache_read: 0.09375 }, - limit: { context: 128000, output: 16384 }, - }, - "llama-3.3-70b": { - id: "llama-3.3-70b", - name: "Llama 3.3 70B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2025-04-06", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 2.8 }, - limit: { context: 128000, output: 4096 }, - }, - "qwen3-next-80b": { - id: "qwen3-next-80b", - name: "Qwen 3 Next 80b", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-04-29", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.35, output: 1.9 }, - limit: { context: 256000, output: 16384 }, - }, - "hermes-3-llama-3.1-405b": { - id: "hermes-3-llama-3.1-405b", - name: "Hermes 3 Llama 3.1 405b", - family: "hermes", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-04", - release_date: "2025-09-25", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.1, output: 3 }, - limit: { context: 128000, output: 16384 }, - }, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-12-10", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.75, output: 3.2, cache_read: 0.375 }, - limit: { context: 256000, output: 65536 }, - }, - "minimax-m21": { - id: "minimax-m21", - name: "MiniMax M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-01", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 1.6, cache_read: 0.04 }, - limit: { context: 198000, output: 32768 }, - }, - "qwen3-5-35b-a3b": { - id: "qwen3-5-35b-a3b", - name: "Qwen 3.5 35B A3B", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-25", - last_updated: "2026-03-09", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3125, output: 1.25, cache_read: 0.15625 }, - limit: { context: 256000, output: 65536 }, - }, - "qwen3-235b-a22b-thinking-2507": { - id: "qwen3-235b-a22b-thinking-2507", - name: "Qwen 3 235B A22B Thinking 2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-04-29", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.45, output: 3.5 }, - limit: { context: 128000, output: 16384 }, - }, - "gemini-3-pro-preview": { - id: "gemini-3-pro-preview", - name: "Gemini 3 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-12-02", - last_updated: "2026-03-12", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 15, cache_read: 0.625 }, - limit: { context: 198000, output: 32768 }, - }, - "llama-3.2-3b": { - id: "llama-3.2-3b", - name: "Llama 3.2 3B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-10-03", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 4096 }, - }, - "venice-uncensored": { - id: "venice-uncensored", - name: "Venice Uncensored 1.1", - family: "venice", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - knowledge: "2023-10", - release_date: "2025-03-18", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.9 }, - limit: { context: 32000, output: 8192 }, - }, - "nvidia-nemotron-3-nano-30b-a3b": { - id: "nvidia-nemotron-3-nano-30b-a3b", - name: "NVIDIA Nemotron 3 Nano 30B", - family: "nemotron", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01-27", - last_updated: "2026-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 128000, output: 16384 }, - }, - "openai-gpt-52-codex": { - id: "openai-gpt-52-codex", - name: "GPT-5.2 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-01-15", - last_updated: "2026-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.19, output: 17.5, cache_read: 0.219 }, - limit: { context: 256000, output: 65536 }, - }, - "qwen3-vl-235b-a22b": { - id: "qwen3-vl-235b-a22b", - name: "Qwen3 VL 235B", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01-16", - last_updated: "2026-03-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 1.5 }, - limit: { context: 256000, output: 16384 }, - }, - "claude-sonnet-45": { - id: "claude-sonnet-45", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-01-15", - last_updated: "2026-01-28", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3.75, output: 18.75, cache_read: 0.375, cache_write: 4.69 }, - limit: { context: 198000, output: 49500 }, - }, - }, - }, - nova: { - id: "nova", - env: ["NOVA_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.nova.amazon.com/v1", - name: "Nova", - doc: "https://nova.amazon.com/dev/documentation", - models: { - "nova-2-lite-v1": { - id: "nova-2-lite-v1", - name: "Nova 2 Lite", - family: "nova-lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text", "image", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, reasoning: 0 }, - limit: { context: 1000000, output: 64000 }, - }, - "nova-2-pro-v1": { - id: "nova-2-pro-v1", - name: "Nova 2 Pro", - family: "nova-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-03", - last_updated: "2026-01-03", - modalities: { input: ["text", "image", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0, reasoning: 0 }, - limit: { context: 1000000, output: 64000 }, - }, - }, - }, - "zai-coding-plan": { - id: "zai-coding-plan", - env: ["ZHIPU_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.z.ai/api/coding/paas/v4", - name: "Z.AI Coding Plan", - doc: "https://docs.z.ai/devpack/overview", - models: { - "glm-5": { - id: "glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "glm-4.7-flashx": { - id: "glm-4.7-flashx", - name: "GLM-4.7-FlashX", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.4, cache_read: 0.01, cache_write: 0 }, - limit: { context: 200000, output: 131072 }, - }, - "glm-4.5-air": { - id: "glm-4.5-air", - name: "GLM-4.5-Air", - family: "glm-air", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 131072, output: 98304 }, - }, - "glm-4.5": { - id: "glm-4.5", - name: "GLM-4.5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 131072, output: 98304 }, - }, - "glm-4.5-flash": { - id: "glm-4.5-flash", - name: "GLM-4.5-Flash", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 131072, output: 98304 }, - }, - "glm-4.7-flash": { - id: "glm-4.7-flash", - name: "GLM-4.7-Flash", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 200000, output: 131072 }, - }, - "glm-4.6": { - id: "glm-4.6", - name: "GLM-4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "glm-4.7": { - id: "glm-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "glm-4.5v": { - id: "glm-4.5v", - name: "GLM-4.5V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-08-11", - last_updated: "2025-08-11", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 64000, output: 16384 }, - }, - "glm-4.6v": { - id: "glm-4.6v", - name: "GLM-4.6V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - }, - }, - "opencode-go": { - id: "opencode-go", - env: ["OPENCODE_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://opencode.ai/zen/go/v1", - name: "OpenCode Go", - doc: "https://opencode.ai/docs/zen", - models: { - "glm-5": { - id: "glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2, cache_read: 0.2 }, - limit: { context: 204800, output: 131072 }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-10", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3, cache_read: 0.1 }, - limit: { context: 262144, output: 65536 }, - }, - "minimax-m2.5": { - id: "minimax-m2.5", - name: "MiniMax M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.03 }, - limit: { context: 204800, output: 131072 }, - provider: { npm: "@ai-sdk/anthropic" }, - }, - }, - }, - drun: { - id: "drun", - env: ["DRUN_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://chat.d.run/v1", - name: "D.Run (China)", - doc: "https://www.d.run", - models: { - "public/deepseek-v3": { - id: "public/deepseek-v3", - name: "DeepSeek V3", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-12-26", - last_updated: "2024-12-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 1.1 }, - limit: { context: 131072, output: 8192 }, - }, - "public/deepseek-r1": { - id: "public/deepseek-r1", - name: "DeepSeek R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-12", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.2 }, - limit: { context: 131072, output: 32000 }, - }, - "public/minimax-m25": { - id: "public/minimax-m25", - name: "MiniMax M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_details" }, - temperature: true, - release_date: "2025-03-01", - last_updated: "2025-03-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 1.16 }, - limit: { context: 204800, output: 131072 }, - }, - }, - }, - firmware: { - id: "firmware", - env: ["FIRMWARE_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://app.firmware.ai/api/v1", - name: "Firmware", - doc: "https://docs.firmware.ai", - models: { - "gpt-5-4": { - id: "gpt-5-4", - name: "GPT-5.4", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 15, cache_read: 0.25 }, - limit: { context: 272000, output: 128000 }, - }, - "glm-5": { - id: "glm-5", - name: "GLM-5", - family: "glm", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-01-20", - last_updated: "2025-02-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 3.2, cache_read: 0.2 }, - limit: { context: 198000, output: 8192 }, - }, - "claude-opus-4-6": { - id: "claude-opus-4-6", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05-31", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 128000 }, - }, - "grok-code-fast-1": { - id: "grok-code-fast-1", - name: "Grok 4.1 Fast (Reasoning)", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2025-08-28", - last_updated: "2025-08-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.5, cache_read: 0.02 }, - limit: { context: 256000, output: 128000 }, - }, - "grok-4-1-fast-reasoning": { - id: "grok-4-1-fast-reasoning", - name: "Grok 4.1 Fast (Reasoning)", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-25", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 128000 }, - }, - "claude-sonnet-4-6": { - id: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2026-02-17", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "gemini-2.5-flash": { - id: "gemini-2.5-flash", - name: "Gemini 2.5 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-07-17", - last_updated: "2025-07-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.075 }, - limit: { context: 1048576, output: 65536 }, - }, - "grok-4-1-fast-non-reasoning": { - id: "grok-4-1-fast-non-reasoning", - name: "Grok 4.1 Fast (Non-Reasoning)", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-25", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 128000 }, - }, - "deepseek-v3-2": { - id: "deepseek-v3-2", - name: "DeepSeek v3.2", - family: "deepseek", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-12-26", - last_updated: "2025-09-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.58, output: 1.68, cache_read: 0.28 }, - limit: { context: 128000, output: 8192 }, - }, - "gemini-3-flash-preview": { - id: "gemini-3-flash-preview", - name: "Gemini 3 Flash Preview", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 3, cache_read: 0.05 }, - limit: { context: 1048576, output: 65536 }, - }, - "gpt-oss-120b": { - id: "gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "1970-01-01", - last_updated: "1970-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 131072, output: 32768 }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi-K2.5", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "1970-01-01", - last_updated: "1970-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 3, cache_read: 0.1 }, - limit: { context: 256000, output: 128000 }, - }, - "gemini-3-1-pro-preview": { - id: "gemini-3-1-pro-preview", - name: "Gemini 3.1 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2026-01", - release_date: "2026-02-18", - last_updated: "2026-02-18", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2 }, - limit: { context: 1000000, output: 64000 }, - }, - "minimax-m2-5": { - id: "minimax-m2-5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2025-01-15", - last_updated: "2025-02-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2, cache_read: 0.03 }, - limit: { context: 192000, output: 8192 }, - }, - "claude-haiku-4-5": { - id: "claude-haiku-4-5", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-opus-4-5": { - id: "claude-opus-4-5", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-11-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "gemini-3-pro-preview": { - id: "gemini-3-pro-preview", - name: "Gemini 3 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2 }, - limit: { context: 1000000, output: 64000 }, - }, - "gpt-5-3-codex": { - id: "gpt-5-3-codex", - name: "GPT-5.3 Codex", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2026-01-31", - release_date: "2026-02-15", - last_updated: "2026-02-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "claude-sonnet-4-5": { - id: "claude-sonnet-4-5", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - name: "GPT-5 Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.03 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-oss-20b": { - id: "gpt-oss-20b", - name: "GPT OSS 20B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "1970-01-01", - last_updated: "1970-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.2 }, - limit: { context: 131072, output: 32768 }, - }, - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31 }, - limit: { context: 1048576, output: 65536 }, - }, - "gpt-5-nano": { - id: "gpt-5-nano", - name: "GPT-5 Nano", - family: "gpt-nano", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4, cache_read: 0.01 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-4o": { - id: "gpt-4o", - name: "GPT-4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-05-13", - last_updated: "2024-08-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10, cache_read: 1.25 }, - limit: { context: 128000, output: 16384 }, - }, - }, - }, - ovhcloud: { - id: "ovhcloud", - env: ["OVHCLOUD_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1", - name: "OVHcloud AI Endpoints", - doc: "https://www.ovhcloud.com/en/public-cloud/ai-endpoints/catalog//", - models: { - "meta-llama-3_3-70b-instruct": { - id: "meta-llama-3_3-70b-instruct", - name: "Meta-Llama-3_3-70B-Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-01", - last_updated: "2025-04-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.74, output: 0.74 }, - limit: { context: 131072, output: 131072 }, - }, - "mistral-7b-instruct-v0.3": { - id: "mistral-7b-instruct-v0.3", - name: "Mistral-7B-Instruct-v0.3", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-01", - last_updated: "2025-04-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.11, output: 0.11 }, - limit: { context: 65536, output: 65536 }, - }, - "mistral-small-3.2-24b-instruct-2506": { - id: "mistral-small-3.2-24b-instruct-2506", - name: "Mistral-Small-3.2-24B-Instruct-2506", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-16", - last_updated: "2025-07-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.31 }, - limit: { context: 131072, output: 131072 }, - }, - "qwen3-32b": { - id: "qwen3-32b", - name: "Qwen3-32B", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-16", - last_updated: "2025-07-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.25 }, - limit: { context: 32768, output: 32768 }, - }, - "qwen2.5-coder-32b-instruct": { - id: "qwen2.5-coder-32b-instruct", - name: "Qwen2.5-Coder-32B-Instruct", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-03-24", - last_updated: "2025-03-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.96, output: 0.96 }, - limit: { context: 32768, output: 32768 }, - }, - "gpt-oss-120b": { - id: "gpt-oss-120b", - name: "gpt-oss-120b", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-08-28", - last_updated: "2025-08-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.47 }, - limit: { context: 131072, output: 131072 }, - }, - "deepseek-r1-distill-llama-70b": { - id: "deepseek-r1-distill-llama-70b", - name: "DeepSeek-R1-Distill-Llama-70B", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-01-30", - last_updated: "2025-01-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.74, output: 0.74 }, - limit: { context: 131072, output: 131072 }, - }, - "qwen2.5-vl-72b-instruct": { - id: "qwen2.5-vl-72b-instruct", - name: "Qwen2.5-VL-72B-Instruct", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-03-31", - last_updated: "2025-03-31", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 1.01, output: 1.01 }, - limit: { context: 32768, output: 32768 }, - }, - "qwen3-coder-30b-a3b-instruct": { - id: "qwen3-coder-30b-a3b-instruct", - name: "Qwen3-Coder-30B-A3B-Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-28", - last_updated: "2025-10-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.26 }, - limit: { context: 262144, output: 262144 }, - }, - "llama-3.1-8b-instruct": { - id: "llama-3.1-8b-instruct", - name: "Llama-3.1-8B-Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-06-11", - last_updated: "2025-06-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.11, output: 0.11 }, - limit: { context: 131072, output: 131072 }, - }, - "mistral-nemo-instruct-2407": { - id: "mistral-nemo-instruct-2407", - name: "Mistral-Nemo-Instruct-2407", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-11-20", - last_updated: "2024-11-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.14, output: 0.14 }, - limit: { context: 65536, output: 65536 }, - }, - "gpt-oss-20b": { - id: "gpt-oss-20b", - name: "gpt-oss-20b", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-08-28", - last_updated: "2025-08-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.18 }, - limit: { context: 131072, output: 131072 }, - }, - "mixtral-8x7b-instruct-v0.1": { - id: "mixtral-8x7b-instruct-v0.1", - name: "Mixtral-8x7B-Instruct-v0.1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-04-01", - last_updated: "2025-04-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 0.7 }, - limit: { context: 32768, output: 32768 }, - }, - }, - }, - stackit: { - id: "stackit", - env: ["STACKIT_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.openai-compat.model-serving.eu01.onstackit.cloud/v1", - name: "STACKIT", - doc: "https://docs.stackit.cloud/products/data-and-ai/ai-model-serving/basics/available-shared-models", - models: { - "intfloat/e5-mistral-7b-instruct": { - id: "intfloat/e5-mistral-7b-instruct", - name: "E5 Mistral 7B", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: false, - release_date: "2023-12-11", - last_updated: "2023-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.02 }, - limit: { context: 4096, output: 4096 }, - }, - "neuralmagic/Meta-Llama-3.1-8B-Instruct-FP8": { - id: "neuralmagic/Meta-Llama-3.1-8B-Instruct-FP8", - name: "Llama 3.1 8B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.16, output: 0.27 }, - limit: { context: 128000, output: 8192 }, - }, - "neuralmagic/Mistral-Nemo-Instruct-2407-FP8": { - id: "neuralmagic/Mistral-Nemo-Instruct-2407-FP8", - name: "Mistral Nemo", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.49, output: 0.71 }, - limit: { context: 128000, output: 8192 }, - }, - "google/gemma-3-27b-it": { - id: "google/gemma-3-27b-it", - name: "Gemma 3 27B", - family: "gemma", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-05-17", - last_updated: "2025-05-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.49, output: 0.71 }, - limit: { context: 37000, output: 8192 }, - }, - "Qwen/Qwen3-VL-Embedding-8B": { - id: "Qwen/Qwen3-VL-Embedding-8B", - name: "Qwen3-VL Embedding 8B", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: false, - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.09 }, - limit: { context: 32000, output: 4096 }, - }, - "Qwen/Qwen3-VL-235B-A22B-Instruct-FP8": { - id: "Qwen/Qwen3-VL-235B-A22B-Instruct-FP8", - name: "Qwen3-VL 235B", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2024-11-01", - last_updated: "2024-11-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 1.64, output: 1.91 }, - limit: { context: 218000, output: 8192 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT-OSS 120B", - family: "gpt", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.49, output: 0.71 }, - limit: { context: 131000, output: 8192 }, - }, - "cortecs/Llama-3.3-70B-Instruct-FP8-Dynamic": { - id: "cortecs/Llama-3.3-70B-Instruct-FP8-Dynamic", - name: "Llama 3.3 70B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2024-12-05", - last_updated: "2024-12-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.49, output: 0.71 }, - limit: { context: 128000, output: 8192 }, - }, - }, - }, - "cloudferro-sherlock": { - id: "cloudferro-sherlock", - env: ["CLOUDFERRO_SHERLOCK_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api-sherlock.cloudferro.com/openai/v1/", - name: "CloudFerro Sherlock", - doc: "https://docs.sherlock.cloudferro.com/", - models: { - "MiniMaxAI/MiniMax-M2.5": { - id: "MiniMaxAI/MiniMax-M2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2026-01", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 196000, output: 196000 }, - }, - "speakleash/Bielik-11B-v2.6-Instruct": { - id: "speakleash/Bielik-11B-v2.6-Instruct", - name: "Bielik 11B v2.6 Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-03-13", - last_updated: "2025-03-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.67, output: 0.67 }, - limit: { context: 32000, output: 32000 }, - }, - "speakleash/Bielik-11B-v3.0-Instruct": { - id: "speakleash/Bielik-11B-v3.0-Instruct", - name: "Bielik 11B v3.0 Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-03-13", - last_updated: "2025-03-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.67, output: 0.67 }, - limit: { context: 32000, output: 32000 }, - }, - "meta-llama/Llama-3.3-70B-Instruct": { - id: "meta-llama/Llama-3.3-70B-Instruct", - name: "Llama 3.3 70B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10-09", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.92, output: 2.92 }, - limit: { context: 70000, output: 70000 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "OpenAI GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-28", - last_updated: "2025-08-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.92, output: 2.92 }, - limit: { context: 131000, output: 131000 }, - }, - }, - }, - requesty: { - id: "requesty", - env: ["REQUESTY_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://router.requesty.ai/v1", - name: "Requesty", - doc: "https://requesty.ai/solution/llm-routing/models", - models: { - "google/gemini-2.5-flash": { - id: "google/gemini-2.5-flash", - name: "Gemini 2.5 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.075, cache_write: 0.55 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-3-flash-preview": { - id: "google/gemini-3-flash-preview", - name: "Gemini 3 Flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 3, cache_read: 0.05, cache_write: 1 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-3-pro-preview": { - id: "google/gemini-3-pro-preview", - name: "Gemini 3 Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, cache_write: 4.5 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-2.5-pro": { - id: "google/gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31, cache_write: 2.375 }, - limit: { context: 1048576, output: 65536 }, - }, - "openai/gpt-5.3-codex": { - id: "openai/gpt-5.3-codex", - name: "GPT-5.3-Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-02-24", - last_updated: "2026-02-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5-codex": { - id: "openai/gpt-5-codex", - name: "GPT-5 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10-01", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5-pro": { - id: "openai/gpt-5-pro", - name: "GPT-5 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-10-06", - last_updated: "2025-10-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 120 }, - limit: { context: 400000, output: 272000 }, - }, - "openai/gpt-4o-mini": { - id: "openai/gpt-4o-mini", - name: "GPT-4o Mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.08 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5.1-codex-max": { - id: "openai/gpt-5.1-codex-max", - name: "GPT-5.1-Codex-Max", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 9, cache_read: 0.11 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.2-codex": { - id: "openai/gpt-5.2-codex", - name: "GPT-5.2-Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-08-31", - release_date: "2026-01-14", - last_updated: "2026-01-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.1": { - id: "openai/gpt-5.1", - name: "GPT-5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.2-chat": { - id: "openai/gpt-5.2-chat", - name: "GPT-5.2 Chat", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5-chat": { - id: "openai/gpt-5-chat", - name: "GPT-5 Chat (latest)", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.1-chat": { - id: "openai/gpt-5.1-chat", - name: "GPT-5.1 Chat", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5-image": { - id: "openai/gpt-5-image", - name: "GPT-5 Image", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10-01", - release_date: "2025-10-14", - last_updated: "2025-10-14", - modalities: { input: ["text", "image", "pdf"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 5, output: 10, cache_read: 1.25 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.1-codex-mini": { - id: "openai/gpt-5.1-codex-mini", - name: "GPT-5.1-Codex-Mini", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.025 }, - limit: { context: 400000, output: 100000 }, - }, - "openai/gpt-5.2": { - id: "openai/gpt-5.2", - name: "GPT-5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-4.1": { - id: "openai/gpt-4.1", - name: "GPT-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/gpt-5": { - id: "openai/gpt-5", - name: "GPT-5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "audio", "image", "video"], output: ["text", "audio", "image"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/o4-mini": { - id: "openai/o4-mini", - name: "o4 Mini", - family: "o-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.28 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4.1-mini": { - id: "openai/gpt-4.1-mini", - name: "GPT-4.1 Mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.6, cache_read: 0.1 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/gpt-5.4": { - id: "openai/gpt-5.4", - name: "GPT-5.4", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 2.5, - output: 15, - cache_read: 0.25, - context_over_200k: { input: 5, output: 22.5, cache_read: 0.5 }, - }, - limit: { context: 1050000, input: 922000, output: 128000 }, - }, - "openai/gpt-5.4-pro": { - id: "openai/gpt-5.4-pro", - name: "GPT-5.4 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 30, output: 180, cache_read: 30 }, - limit: { context: 1050000, input: 922000, output: 128000 }, - }, - "openai/gpt-5.1-codex": { - id: "openai/gpt-5.1-codex", - name: "GPT-5.1-Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.2-pro": { - id: "openai/gpt-5.2-pro", - name: "GPT-5.2 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 21, output: 168 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5-mini": { - id: "openai/gpt-5-mini", - name: "GPT-5 Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.03 }, - limit: { context: 128000, output: 32000 }, - }, - "openai/gpt-5-nano": { - id: "openai/gpt-5-nano", - name: "GPT-5 Nano", - family: "gpt-nano", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4, cache_read: 0.01 }, - limit: { context: 16000, output: 4000 }, - }, - "anthropic/claude-3-7-sonnet": { - id: "anthropic/claude-3-7-sonnet", - name: "Claude Sonnet 3.7", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-01", - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-opus-4-1": { - id: "anthropic/claude-opus-4-1", - name: "Claude Opus 4.1", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "anthropic/claude-opus-4-6": { - id: "anthropic/claude-opus-4-6", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-05-30", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 5, - output: 25, - cache_read: 0.5, - cache_write: 6.25, - context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 }, - }, - limit: { context: 1000000, output: 128000 }, - }, - "anthropic/claude-sonnet-4-6": { - id: "anthropic/claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 1000000, output: 128000 }, - }, - "anthropic/claude-opus-4": { - id: "anthropic/claude-opus-4", - name: "Claude Opus 4", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "anthropic/claude-haiku-4-5": { - id: "anthropic/claude-haiku-4-5", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-01", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 62000 }, - }, - "anthropic/claude-opus-4-5": { - id: "anthropic/claude-opus-4-5", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-11-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-sonnet-4": { - id: "anthropic/claude-sonnet-4", - name: "Claude Sonnet 4", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-sonnet-4-5": { - id: "anthropic/claude-sonnet-4-5", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 1000000, output: 64000 }, - }, - "xai/grok-4-fast": { - id: "xai/grok-4-fast", - name: "Grok 4 Fast", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-19", - last_updated: "2025-09-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05, cache_write: 0.2 }, - limit: { context: 2000000, output: 64000 }, - }, - "xai/grok-4": { - id: "xai/grok-4", - name: "Grok 4", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-09", - last_updated: "2025-09-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75, cache_write: 3 }, - limit: { context: 256000, output: 64000 }, - }, - }, - }, - "qihang-ai": { - id: "qihang-ai", - env: ["QIHANG_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.qhaigc.net/v1", - name: "QiHang", - doc: "https://www.qhaigc.net/docs", - models: { - "claude-opus-4-5-20251101": { - id: "claude-opus-4-5-20251101", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-11-01", - last_updated: "2025-11-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.71, output: 3.57 }, - limit: { context: 200000, output: 32000 }, - }, - "gpt-5.2-codex": { - id: "gpt-5.2-codex", - name: "GPT-5.2 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 1.14 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "gemini-2.5-flash": { - id: "gemini-2.5-flash", - name: "Gemini 2.5 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09, output: 0.71, context_over_200k: { input: 0.09, output: 0.71 } }, - limit: { context: 1048576, output: 65536 }, - }, - "gemini-3-flash-preview": { - id: "gemini-3-flash-preview", - name: "Gemini 3 Flash Preview", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.43, context_over_200k: { input: 0.07, output: 0.43 } }, - limit: { context: 1048576, output: 65536 }, - }, - "claude-sonnet-4-5-20250929": { - id: "claude-sonnet-4-5-20250929", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.43, output: 2.14 }, - limit: { context: 200000, output: 64000 }, - }, - "gpt-5.2": { - id: "gpt-5.2", - name: "GPT-5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "claude-haiku-4-5-20251001": { - id: "claude-haiku-4-5-20251001", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-10-01", - last_updated: "2025-10-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.71 }, - limit: { context: 200000, output: 64000 }, - }, - "gemini-3-pro-preview": { - id: "gemini-3-pro-preview", - name: "Gemini 3 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.57, output: 3.43 }, - limit: { context: 1000000, output: 65000 }, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - name: "GPT-5-Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.04, output: 0.29 }, - limit: { context: 200000, output: 64000 }, - }, - }, - }, - "siliconflow-cn": { - id: "siliconflow-cn", - env: ["SILICONFLOW_CN_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.siliconflow.cn/v1", - name: "SiliconFlow (China)", - doc: "https://cloud.siliconflow.com/models", - models: { - "zai-org/GLM-4.6V": { - id: "zai-org/GLM-4.6V", - name: "zai-org/GLM-4.6V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-07", - last_updated: "2025-12-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 131000, output: 131000 }, - }, - "zai-org/GLM-4.5V": { - id: "zai-org/GLM-4.5V", - name: "zai-org/GLM-4.5V", - family: "glm", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-13", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.86 }, - limit: { context: 66000, output: 66000 }, - }, - "zai-org/GLM-4.6": { - id: "zai-org/GLM-4.6", - name: "zai-org/GLM-4.6", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-04", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.9 }, - limit: { context: 205000, output: 205000 }, - }, - "zai-org/GLM-4.5-Air": { - id: "zai-org/GLM-4.5-Air", - name: "zai-org/GLM-4.5-Air", - family: "glm-air", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-28", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.86 }, - limit: { context: 131000, output: 131000 }, - }, - "Pro/zai-org/GLM-4.7": { - id: "Pro/zai-org/GLM-4.7", - name: "Pro/zai-org/GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 205000, output: 205000 }, - }, - "Pro/zai-org/GLM-5": { - id: "Pro/zai-org/GLM-5", - name: "Pro/zai-org/GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2 }, - limit: { context: 205000, output: 205000 }, - }, - "Pro/MiniMaxAI/MiniMax-M2.5": { - id: "Pro/MiniMaxAI/MiniMax-M2.5", - name: "Pro/MiniMaxAI/MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: false, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-13", - last_updated: "2026-02-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.22 }, - limit: { context: 192000, output: 131000 }, - }, - "Pro/MiniMaxAI/MiniMax-M2.1": { - id: "Pro/MiniMaxAI/MiniMax-M2.1", - name: "Pro/MiniMaxAI/MiniMax-M2.1", - family: "minimax", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 197000, output: 131000 }, - }, - "Pro/deepseek-ai/DeepSeek-R1": { - id: "Pro/deepseek-ai/DeepSeek-R1", - name: "Pro/deepseek-ai/DeepSeek-R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-05-28", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 2.18 }, - limit: { context: 164000, output: 164000 }, - }, - "Pro/deepseek-ai/DeepSeek-V3.2": { - id: "Pro/deepseek-ai/DeepSeek-V3.2", - name: "Pro/deepseek-ai/DeepSeek-V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-03", - last_updated: "2025-12-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.42 }, - limit: { context: 164000, output: 164000 }, - }, - "Pro/deepseek-ai/DeepSeek-V3": { - id: "Pro/deepseek-ai/DeepSeek-V3", - name: "Pro/deepseek-ai/DeepSeek-V3", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-12-26", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1 }, - limit: { context: 164000, output: 164000 }, - }, - "Pro/deepseek-ai/DeepSeek-V3.1-Terminus": { - id: "Pro/deepseek-ai/DeepSeek-V3.1-Terminus", - name: "Pro/deepseek-ai/DeepSeek-V3.1-Terminus", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-29", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 1 }, - limit: { context: 164000, output: 164000 }, - }, - "Pro/moonshotai/Kimi-K2-Instruct-0905": { - id: "Pro/moonshotai/Kimi-K2-Instruct-0905", - name: "Pro/moonshotai/Kimi-K2-Instruct-0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-08", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 262000, output: 262000 }, - }, - "Pro/moonshotai/Kimi-K2.5": { - id: "Pro/moonshotai/Kimi-K2.5", - name: "Pro/moonshotai/Kimi-K2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.55, output: 3 }, - limit: { context: 262000, output: 262000 }, - }, - "Pro/moonshotai/Kimi-K2-Thinking": { - id: "Pro/moonshotai/Kimi-K2-Thinking", - name: "Pro/moonshotai/Kimi-K2-Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-11-07", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.55, output: 2.5 }, - limit: { context: 262000, output: 262000 }, - }, - "PaddlePaddle/PaddleOCR-VL-1.5": { - id: "PaddlePaddle/PaddleOCR-VL-1.5", - name: "PaddlePaddle/PaddleOCR-VL-1.5", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2026-01-29", - last_updated: "2026-01-29", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 16384, output: 16384 }, - }, - "PaddlePaddle/PaddleOCR-VL": { - id: "PaddlePaddle/PaddleOCR-VL", - name: "PaddlePaddle/PaddleOCR-VL", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-10-16", - last_updated: "2025-10-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 16384, output: 16384 }, - }, - "Kwaipilot/KAT-Dev": { - id: "Kwaipilot/KAT-Dev", - name: "Kwaipilot/KAT-Dev", - family: "kat-coder", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-27", - last_updated: "2026-01-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 128000, output: 128000 }, - }, - "deepseek-ai/DeepSeek-OCR": { - id: "deepseek-ai/DeepSeek-OCR", - name: "deepseek-ai/DeepSeek-OCR", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-10-20", - last_updated: "2025-10-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 8192, output: 8192 }, - }, - "deepseek-ai/DeepSeek-V3.1-Terminus": { - id: "deepseek-ai/DeepSeek-V3.1-Terminus", - name: "deepseek-ai/DeepSeek-V3.1-Terminus", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-29", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 1 }, - limit: { context: 164000, output: 164000 }, - }, - "deepseek-ai/DeepSeek-V3": { - id: "deepseek-ai/DeepSeek-V3", - name: "deepseek-ai/DeepSeek-V3", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-12-26", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1 }, - limit: { context: 164000, output: 164000 }, - }, - "deepseek-ai/DeepSeek-V3.2": { - id: "deepseek-ai/DeepSeek-V3.2", - name: "deepseek-ai/DeepSeek-V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-03", - last_updated: "2025-12-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.42 }, - limit: { context: 164000, output: 164000 }, - }, - "deepseek-ai/deepseek-vl2": { - id: "deepseek-ai/deepseek-vl2", - name: "deepseek-ai/deepseek-vl2", - family: "deepseek", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-12-13", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 4000, output: 4000 }, - }, - "deepseek-ai/DeepSeek-R1": { - id: "deepseek-ai/DeepSeek-R1", - name: "deepseek-ai/DeepSeek-R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-05-28", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 2.18 }, - limit: { context: 164000, output: 164000 }, - }, - "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B": { - id: "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", - name: "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-01-20", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 131000, output: 131000 }, - }, - "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B": { - id: "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", - name: "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-01-20", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0.18 }, - limit: { context: 131000, output: 131000 }, - }, - "ByteDance-Seed/Seed-OSS-36B-Instruct": { - id: "ByteDance-Seed/Seed-OSS-36B-Instruct", - name: "ByteDance-Seed/Seed-OSS-36B-Instruct", - family: "seed", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-04", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.21, output: 0.57 }, - limit: { context: 262000, output: 262000 }, - }, - "tencent/Hunyuan-MT-7B": { - id: "tencent/Hunyuan-MT-7B", - name: "tencent/Hunyuan-MT-7B", - family: "hunyuan", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 33000, output: 33000 }, - }, - "tencent/Hunyuan-A13B-Instruct": { - id: "tencent/Hunyuan-A13B-Instruct", - name: "tencent/Hunyuan-A13B-Instruct", - family: "hunyuan", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-06-30", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.57 }, - limit: { context: 131000, output: 131000 }, - }, - "ascend-tribe/pangu-pro-moe": { - id: "ascend-tribe/pangu-pro-moe", - name: "ascend-tribe/pangu-pro-moe", - family: "pangu", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-07-02", - last_updated: "2026-01-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 128000, output: 128000 }, - }, - "moonshotai/Kimi-K2-Thinking": { - id: "moonshotai/Kimi-K2-Thinking", - name: "moonshotai/Kimi-K2-Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-11-07", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.55, output: 2.5 }, - limit: { context: 262000, output: 262000 }, - }, - "moonshotai/Kimi-K2-Instruct-0905": { - id: "moonshotai/Kimi-K2-Instruct-0905", - name: "moonshotai/Kimi-K2-Instruct-0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-08", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 262000, output: 262000 }, - }, - "inclusionAI/Ling-mini-2.0": { - id: "inclusionAI/Ling-mini-2.0", - name: "inclusionAI/Ling-mini-2.0", - family: "ling", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-10", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.28 }, - limit: { context: 131000, output: 131000 }, - }, - "inclusionAI/Ring-flash-2.0": { - id: "inclusionAI/Ring-flash-2.0", - name: "inclusionAI/Ring-flash-2.0", - family: "ring", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-29", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.57 }, - limit: { context: 131000, output: 131000 }, - }, - "inclusionAI/Ling-flash-2.0": { - id: "inclusionAI/Ling-flash-2.0", - name: "inclusionAI/Ling-flash-2.0", - family: "ling", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.57 }, - limit: { context: 131000, output: 131000 }, - }, - "baidu/ERNIE-4.5-300B-A47B": { - id: "baidu/ERNIE-4.5-300B-A47B", - name: "baidu/ERNIE-4.5-300B-A47B", - family: "ernie", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-02", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.28, output: 1.1 }, - limit: { context: 131000, output: 131000 }, - }, - "stepfun-ai/Step-3.5-Flash": { - id: "stepfun-ai/Step-3.5-Flash", - name: "stepfun-ai/Step-3.5-Flash", - family: "step", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3.5-9B": { - id: "Qwen/Qwen3.5-9B", - name: "Qwen/Qwen3.5-9B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-03-03", - last_updated: "2026-03-03", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.22, output: 1.74 }, - limit: { context: 262144, output: 65536 }, - }, - "Qwen/Qwen3.5-122B-A10B": { - id: "Qwen/Qwen3.5-122B-A10B", - name: "Qwen/Qwen3.5-122B-A10B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-26", - last_updated: "2026-02-26", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.29, output: 2.32 }, - limit: { context: 262144, output: 65536 }, - }, - "Qwen/Qwen3.5-397B-A17B": { - id: "Qwen/Qwen3.5-397B-A17B", - name: "Qwen/Qwen3.5-397B-A17B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-16", - last_updated: "2026-02-16", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.29, output: 1.74 }, - limit: { context: 262144, output: 65536 }, - }, - "Qwen/Qwen3.5-35B-A3B": { - id: "Qwen/Qwen3.5-35B-A3B", - name: "Qwen/Qwen3.5-35B-A3B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-25", - last_updated: "2026-02-25", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.23, output: 1.86 }, - limit: { context: 262144, output: 65536 }, - }, - "Qwen/Qwen3.5-4B": { - id: "Qwen/Qwen3.5-4B", - name: "Qwen/Qwen3.5-4B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-03-03", - last_updated: "2026-03-03", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 65536 }, - }, - "Qwen/Qwen3.5-27B": { - id: "Qwen/Qwen3.5-27B", - name: "Qwen/Qwen3.5-27B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-25", - last_updated: "2026-02-25", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.26, output: 2.09 }, - limit: { context: 262144, output: 65536 }, - }, - "Qwen/Qwen2.5-VL-32B-Instruct": { - id: "Qwen/Qwen2.5-VL-32B-Instruct", - name: "Qwen/Qwen2.5-VL-32B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-03-24", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.27 }, - limit: { context: 131000, output: 131000 }, - }, - "Qwen/Qwen3-14B": { - id: "Qwen/Qwen3-14B", - name: "Qwen/Qwen3-14B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-30", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.28 }, - limit: { context: 131000, output: 131000 }, - }, - "Qwen/Qwen3-235B-A22B-Instruct-2507": { - id: "Qwen/Qwen3-235B-A22B-Instruct-2507", - name: "Qwen/Qwen3-235B-A22B-Instruct-2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-23", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09, output: 0.6 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-VL-32B-Thinking": { - id: "Qwen/Qwen3-VL-32B-Thinking", - name: "Qwen/Qwen3-VL-32B-Thinking", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-21", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.5 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-Coder-30B-A3B-Instruct": { - id: "Qwen/Qwen3-Coder-30B-A3B-Instruct", - name: "Qwen/Qwen3-Coder-30B-A3B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-01", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.28 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-VL-8B-Thinking": { - id: "Qwen/Qwen3-VL-8B-Thinking", - name: "Qwen/Qwen3-VL-8B-Thinking", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-15", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 2 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-VL-30B-A3B-Instruct": { - id: "Qwen/Qwen3-VL-30B-A3B-Instruct", - name: "Qwen/Qwen3-VL-30B-A3B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-05", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 1 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-Omni-30B-A3B-Captioner": { - id: "Qwen/Qwen3-Omni-30B-A3B-Captioner", - name: "Qwen/Qwen3-Omni-30B-A3B-Captioner", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-04", - last_updated: "2025-11-25", - modalities: { input: ["audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 66000, output: 66000 }, - }, - "Qwen/Qwen3-Next-80B-A3B-Thinking": { - id: "Qwen/Qwen3-Next-80B-A3B-Thinking", - name: "Qwen/Qwen3-Next-80B-A3B-Thinking", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-25", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.57 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-VL-8B-Instruct": { - id: "Qwen/Qwen3-VL-8B-Instruct", - name: "Qwen/Qwen3-VL-8B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-15", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0.68 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen2.5-72B-Instruct-128K": { - id: "Qwen/Qwen2.5-72B-Instruct-128K", - name: "Qwen/Qwen2.5-72B-Instruct-128K", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.59, output: 0.59 }, - limit: { context: 131000, output: 4000 }, - }, - "Qwen/Qwen2.5-72B-Instruct": { - id: "Qwen/Qwen2.5-72B-Instruct", - name: "Qwen/Qwen2.5-72B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.59, output: 0.59 }, - limit: { context: 33000, output: 4000 }, - }, - "Qwen/Qwen2.5-VL-72B-Instruct": { - id: "Qwen/Qwen2.5-VL-72B-Instruct", - name: "Qwen/Qwen2.5-VL-72B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-01-28", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.59, output: 0.59 }, - limit: { context: 131000, output: 4000 }, - }, - "Qwen/Qwen2.5-14B-Instruct": { - id: "Qwen/Qwen2.5-14B-Instruct", - name: "Qwen/Qwen2.5-14B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 33000, output: 4000 }, - }, - "Qwen/Qwen2.5-7B-Instruct": { - id: "Qwen/Qwen2.5-7B-Instruct", - name: "Qwen/Qwen2.5-7B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.05 }, - limit: { context: 33000, output: 4000 }, - }, - "Qwen/Qwen3-Omni-30B-A3B-Thinking": { - id: "Qwen/Qwen3-Omni-30B-A3B-Thinking", - name: "Qwen/Qwen3-Omni-30B-A3B-Thinking", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-04", - last_updated: "2025-11-25", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 66000, output: 66000 }, - }, - "Qwen/Qwen3-Coder-480B-A35B-Instruct": { - id: "Qwen/Qwen3-Coder-480B-A35B-Instruct", - name: "Qwen/Qwen3-Coder-480B-A35B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-31", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-8B": { - id: "Qwen/Qwen3-8B", - name: "Qwen/Qwen3-8B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-30", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.06, output: 0.06 }, - limit: { context: 131000, output: 131000 }, - }, - "Qwen/Qwen2.5-Coder-32B-Instruct": { - id: "Qwen/Qwen2.5-Coder-32B-Instruct", - name: "Qwen/Qwen2.5-Coder-32B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-11-11", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0.18 }, - limit: { context: 33000, output: 4000 }, - }, - "Qwen/Qwen2.5-32B-Instruct": { - id: "Qwen/Qwen2.5-32B-Instruct", - name: "Qwen/Qwen2.5-32B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-09-19", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0.18 }, - limit: { context: 33000, output: 4000 }, - }, - "Qwen/Qwen3-30B-A3B-Thinking-2507": { - id: "Qwen/Qwen3-30B-A3B-Thinking-2507", - name: "Qwen/Qwen3-30B-A3B-Thinking-2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-31", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09, output: 0.3 }, - limit: { context: 262000, output: 131000 }, - }, - "Qwen/Qwen3-Omni-30B-A3B-Instruct": { - id: "Qwen/Qwen3-Omni-30B-A3B-Instruct", - name: "Qwen/Qwen3-Omni-30B-A3B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-04", - last_updated: "2025-11-25", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 66000, output: 66000 }, - }, - "Qwen/Qwen3-235B-A22B-Thinking-2507": { - id: "Qwen/Qwen3-235B-A22B-Thinking-2507", - name: "Qwen/Qwen3-235B-A22B-Thinking-2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-28", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.13, output: 0.6 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-Next-80B-A3B-Instruct": { - id: "Qwen/Qwen3-Next-80B-A3B-Instruct", - name: "Qwen/Qwen3-Next-80B-A3B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 1.4 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-VL-235B-A22B-Thinking": { - id: "Qwen/Qwen3-VL-235B-A22B-Thinking", - name: "Qwen/Qwen3-VL-235B-A22B-Thinking", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-04", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.45, output: 3.5 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-32B": { - id: "Qwen/Qwen3-32B", - name: "Qwen/Qwen3-32B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-30", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.57 }, - limit: { context: 131000, output: 131000 }, - }, - "Qwen/QwQ-32B": { - id: "Qwen/QwQ-32B", - name: "Qwen/QwQ-32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-03-06", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.58 }, - limit: { context: 131000, output: 131000 }, - }, - "Qwen/Qwen3-VL-32B-Instruct": { - id: "Qwen/Qwen3-VL-32B-Instruct", - name: "Qwen/Qwen3-VL-32B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-21", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-VL-235B-A22B-Instruct": { - id: "Qwen/Qwen3-VL-235B-A22B-Instruct", - name: "Qwen/Qwen3-VL-235B-A22B-Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-04", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.5 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-30B-A3B-Instruct-2507": { - id: "Qwen/Qwen3-30B-A3B-Instruct-2507", - name: "Qwen/Qwen3-30B-A3B-Instruct-2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-30", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09, output: 0.3 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen3-VL-30B-A3B-Thinking": { - id: "Qwen/Qwen3-VL-30B-A3B-Thinking", - name: "Qwen/Qwen3-VL-30B-A3B-Thinking", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-11", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 1 }, - limit: { context: 262000, output: 262000 }, - }, - "THUDM/GLM-Z1-9B-0414": { - id: "THUDM/GLM-Z1-9B-0414", - name: "THUDM/GLM-Z1-9B-0414", - family: "glm-z", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.086, output: 0.086 }, - limit: { context: 131000, output: 131000 }, - }, - "THUDM/GLM-Z1-32B-0414": { - id: "THUDM/GLM-Z1-32B-0414", - name: "THUDM/GLM-Z1-32B-0414", - family: "glm-z", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.57 }, - limit: { context: 131000, output: 131000 }, - }, - "THUDM/GLM-4-9B-0414": { - id: "THUDM/GLM-4-9B-0414", - name: "THUDM/GLM-4-9B-0414", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.086, output: 0.086 }, - limit: { context: 33000, output: 33000 }, - }, - "THUDM/GLM-4-32B-0414": { - id: "THUDM/GLM-4-32B-0414", - name: "THUDM/GLM-4-32B-0414", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-04-18", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.27 }, - limit: { context: 33000, output: 33000 }, - }, - }, - }, - helicone: { - id: "helicone", - env: ["HELICONE_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://ai-gateway.helicone.ai/v1", - name: "Helicone", - doc: "https://helicone.ai/models", - models: { - "claude-4.5-haiku": { - id: "claude-4.5-haiku", - name: "Anthropic: Claude 4.5 Haiku", - family: "claude-haiku", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-10", - release_date: "2025-10-01", - last_updated: "2025-10-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.09999999999999999, cache_write: 1.25 }, - limit: { context: 200000, output: 8192 }, - }, - "gpt-5-codex": { - id: "gpt-5-codex", - name: "OpenAI: GPT-5 Codex", - family: "gpt-codex", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.12500000000000003 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-5-pro": { - id: "gpt-5-pro", - name: "OpenAI: GPT-5 Pro", - family: "gpt-pro", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 120 }, - limit: { context: 128000, output: 32768 }, - }, - "deepseek-reasoner": { - id: "deepseek-reasoner", - name: "DeepSeek Reasoner", - family: "deepseek-thinking", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.56, output: 1.68, cache_read: 0.07 }, - limit: { context: 128000, output: 64000 }, - }, - "claude-3.7-sonnet": { - id: "claude-3.7-sonnet", - name: "Anthropic: Claude 3.7 Sonnet", - family: "claude-sonnet", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-02", - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.30000000000000004, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "gpt-4o-mini": { - id: "gpt-4o-mini", - name: "OpenAI GPT-4o-mini", - family: "gpt-mini", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.075 }, - limit: { context: 128000, output: 16384 }, - }, - "grok-4-fast-reasoning": { - id: "grok-4-fast-reasoning", - name: "xAI: Grok 4 Fast Reasoning", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-01", - last_updated: "2025-09-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.19999999999999998, output: 0.5, cache_read: 0.049999999999999996 }, - limit: { context: 2000000, output: 2000000 }, - }, - "gpt-5-chat-latest": { - id: "gpt-5-chat-latest", - name: "OpenAI GPT-5 Chat Latest", - family: "gpt-codex", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2024-09", - release_date: "2024-09-30", - last_updated: "2024-09-30", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.12500000000000003 }, - limit: { context: 128000, output: 16384 }, - }, - "llama-4-scout": { - id: "llama-4-scout", - name: "Meta Llama 4 Scout 17B 16E", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.08, output: 0.3 }, - limit: { context: 131072, output: 8192 }, - }, - "codex-mini-latest": { - id: "codex-mini-latest", - name: "OpenAI Codex Mini Latest", - family: "gpt-codex-mini", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 6, cache_read: 0.375 }, - limit: { context: 200000, output: 100000 }, - }, - "qwen2.5-coder-7b-fast": { - id: "qwen2.5-coder-7b-fast", - name: "Qwen2.5 Coder 7B fast", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-09", - release_date: "2024-09-15", - last_updated: "2024-09-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.03, output: 0.09 }, - limit: { context: 32000, output: 8192 }, - }, - "claude-opus-4-1": { - id: "claude-opus-4-1", - name: "Anthropic: Claude Opus 4.1", - family: "claude-opus", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "sonar-reasoning-pro": { - id: "sonar-reasoning-pro", - name: "Perplexity Sonar Reasoning Pro", - family: "sonar-reasoning", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-27", - last_updated: "2025-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8 }, - limit: { context: 127000, output: 4096 }, - }, - "deepseek-v3": { - id: "deepseek-v3", - name: "DeepSeek V3", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2024-12-26", - last_updated: "2024-12-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.56, output: 1.68, cache_read: 0.07 }, - limit: { context: 128000, output: 8192 }, - }, - "llama-3.1-8b-instruct-turbo": { - id: "llama-3.1-8b-instruct-turbo", - name: "Meta Llama 3.1 8B Instruct Turbo", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0.03 }, - limit: { context: 128000, output: 128000 }, - }, - "grok-3": { - id: "grok-3", - name: "xAI Grok 3", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2024-06-01", - last_updated: "2024-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75 }, - limit: { context: 131072, output: 131072 }, - }, - "ernie-4.5-21b-a3b-thinking": { - id: "ernie-4.5-21b-a3b-thinking", - name: "Baidu Ernie 4.5 21B A3B Thinking", - family: "ernie", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-03", - release_date: "2025-03-16", - last_updated: "2025-03-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.28 }, - limit: { context: 128000, output: 8000 }, - }, - "grok-code-fast-1": { - id: "grok-code-fast-1", - name: "xAI Grok Code Fast 1", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-08-25", - last_updated: "2024-08-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.19999999999999998, output: 1.5, cache_read: 0.02 }, - limit: { context: 256000, output: 10000 }, - }, - "llama-prompt-guard-2-22m": { - id: "llama-prompt-guard-2-22m", - name: "Meta Llama Prompt Guard 2 22M", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2024-10-01", - last_updated: "2024-10-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.01, output: 0.01 }, - limit: { context: 512, output: 2 }, - }, - "llama-3.3-70b-instruct": { - id: "llama-3.3-70b-instruct", - name: "Meta Llama 3.3 70B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.13, output: 0.39 }, - limit: { context: 128000, output: 16400 }, - }, - "grok-4-1-fast-reasoning": { - id: "grok-4-1-fast-reasoning", - name: "xAI Grok 4.1 Fast Reasoning", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-17", - last_updated: "2025-11-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.19999999999999998, output: 0.5, cache_read: 0.049999999999999996 }, - limit: { context: 2000000, output: 2000000 }, - }, - "claude-4.5-sonnet": { - id: "claude-4.5-sonnet", - name: "Anthropic: Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.30000000000000004, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "gpt-4.1-mini-2025-04-14": { - id: "gpt-4.1-mini-2025-04-14", - name: "OpenAI GPT-4.1 Mini", - family: "gpt-mini", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.39999999999999997, output: 1.5999999999999999, cache_read: 0.09999999999999999 }, - limit: { context: 1047576, output: 32768 }, - }, - "gemini-2.5-flash": { - id: "gemini-2.5-flash", - name: "Google Gemini 2.5 Flash", - family: "gemini-flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.075, cache_write: 0.3 }, - limit: { context: 1048576, output: 65535 }, - }, - "llama-guard-4": { - id: "llama-guard-4", - name: "Meta Llama Guard 4 12B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.21, output: 0.21 }, - limit: { context: 131072, output: 1024 }, - }, - "grok-4-1-fast-non-reasoning": { - id: "grok-4-1-fast-non-reasoning", - name: "xAI Grok 4.1 Fast Non-Reasoning", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-17", - last_updated: "2025-11-17", - modalities: { input: ["text", "image"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 0.19999999999999998, output: 0.5, cache_read: 0.049999999999999996 }, - limit: { context: 2000000, output: 30000 }, - }, - o1: { - id: "o1", - name: "OpenAI: o1", - family: "o", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 60, cache_read: 7.5 }, - limit: { context: 200000, output: 100000 }, - }, - "gpt-5.1": { - id: "gpt-5.1", - name: "OpenAI GPT-5.1", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.12500000000000003 }, - limit: { context: 400000, output: 128000 }, - }, - "kimi-k2-0905": { - id: "kimi-k2-0905", - name: "Kimi K2 (09/05)", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 2, cache_read: 0.39999999999999997 }, - limit: { context: 262144, output: 16384 }, - }, - "grok-4": { - id: "grok-4", - name: "xAI Grok 4", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-07-09", - last_updated: "2024-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75 }, - limit: { context: 256000, output: 256000 }, - }, - "llama-3.1-8b-instant": { - id: "llama-3.1-8b-instant", - name: "Meta Llama 3.1 8B Instant", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.049999999999999996, output: 0.08 }, - limit: { context: 131072, output: 32678 }, - }, - sonar: { - id: "sonar", - name: "Perplexity Sonar", - family: "sonar", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-27", - last_updated: "2025-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 1 }, - limit: { context: 127000, output: 4096 }, - }, - o3: { - id: "o3", - name: "OpenAI o3", - family: "o", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2024-06", - release_date: "2024-06-01", - last_updated: "2024-06-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 200000, output: 100000 }, - }, - "qwen3-coder": { - id: "qwen3-coder", - name: "Qwen3 Coder 480B A35B Instruct Turbo", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.22, output: 0.95 }, - limit: { context: 262144, output: 16384 }, - }, - "glm-4.6": { - id: "glm-4.6", - name: "Zai GLM-4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.44999999999999996, output: 1.5 }, - limit: { context: 204800, output: 131072 }, - }, - "sonar-reasoning": { - id: "sonar-reasoning", - name: "Perplexity Sonar Reasoning", - family: "sonar-reasoning", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-27", - last_updated: "2025-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5 }, - limit: { context: 127000, output: 4096 }, - }, - "qwen3-32b": { - id: "qwen3-32b", - name: "Qwen3 32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-28", - last_updated: "2025-04-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.29, output: 0.59 }, - limit: { context: 131072, output: 40960 }, - }, - "sonar-deep-research": { - id: "sonar-deep-research", - name: "Perplexity Sonar Deep Research", - family: "sonar-deep-research", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-27", - last_updated: "2025-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8 }, - limit: { context: 127000, output: 4096 }, - }, - "gpt-4.1-nano": { - id: "gpt-4.1-nano", - name: "OpenAI GPT-4.1 Nano", - family: "gpt-nano", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09999999999999999, output: 0.39999999999999997, cache_read: 0.024999999999999998 }, - limit: { context: 1047576, output: 32768 }, - }, - "claude-sonnet-4-5-20250929": { - id: "claude-sonnet-4-5-20250929", - name: "Anthropic: Claude Sonnet 4.5 (20250929)", - family: "claude-sonnet", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.30000000000000004, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "gemini-2.5-flash-lite": { - id: "gemini-2.5-flash-lite", - name: "Google Gemini 2.5 Flash Lite", - family: "gemini-flash-lite", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-22", - last_updated: "2025-07-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { - input: 0.09999999999999999, - output: 0.39999999999999997, - cache_read: 0.024999999999999998, - cache_write: 0.09999999999999999, - }, - limit: { context: 1048576, output: 65535 }, - }, - "claude-3.5-haiku": { - id: "claude-3.5-haiku", - name: "Anthropic: Claude 3.5 Haiku", - family: "claude-haiku", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7999999999999999, output: 4, cache_read: 0.08, cache_write: 1 }, - limit: { context: 200000, output: 8192 }, - }, - "gpt-oss-120b": { - id: "gpt-oss-120b", - name: "OpenAI GPT-OSS 120b", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2024-06-01", - last_updated: "2024-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.04, output: 0.16 }, - limit: { context: 131072, output: 131072 }, - }, - "gpt-5.1-codex-mini": { - id: "gpt-5.1-codex-mini", - name: "OpenAI: GPT-5.1 Codex Mini", - family: "gpt-codex", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.024999999999999998 }, - limit: { context: 400000, output: 128000 }, - }, - "deepseek-r1-distill-llama-70b": { - id: "deepseek-r1-distill-llama-70b", - name: "DeepSeek R1 Distill Llama 70B", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.03, output: 0.13 }, - limit: { context: 128000, output: 4096 }, - }, - "deepseek-v3.1-terminus": { - id: "deepseek-v3.1-terminus", - name: "DeepSeek V3.1 Terminus", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-22", - last_updated: "2025-09-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 1, cache_read: 0.21600000000000003 }, - limit: { context: 128000, output: 16384 }, - }, - "gpt-4.1": { - id: "gpt-4.1", - name: "OpenAI GPT-4.1", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 1047576, output: 32768 }, - }, - "claude-3.5-sonnet-v2": { - id: "claude-3.5-sonnet-v2", - name: "Anthropic: Claude 3.5 Sonnet v2", - family: "claude-sonnet", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.30000000000000004, cache_write: 3.75 }, - limit: { context: 200000, output: 8192 }, - }, - "mistral-small": { - id: "mistral-small", - name: "Mistral Small", - family: "mistral-small", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-02", - release_date: "2024-02-26", - last_updated: "2024-02-26", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 75, output: 200 }, - limit: { context: 128000, output: 128000 }, - }, - "o3-pro": { - id: "o3-pro", - name: "OpenAI o3 Pro", - family: "o-pro", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2024-06", - release_date: "2024-06-01", - last_updated: "2024-06-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 20, output: 80 }, - limit: { context: 200000, output: 100000 }, - }, - "mistral-nemo": { - id: "mistral-nemo", - name: "Mistral Nemo", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-07", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 20, output: 40 }, - limit: { context: 128000, output: 16400 }, - }, - "qwen3-coder-30b-a3b-instruct": { - id: "qwen3-coder-30b-a3b-instruct", - name: "Qwen3 Coder 30B A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-31", - last_updated: "2025-07-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09999999999999999, output: 0.3 }, - limit: { context: 262144, output: 262144 }, - }, - "qwen3-vl-235b-a22b-instruct": { - id: "qwen3-vl-235b-a22b-instruct", - name: "Qwen3 VL 235B A22B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-23", - last_updated: "2025-09-23", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.5 }, - limit: { context: 256000, output: 16384 }, - }, - "qwen3-235b-a22b-thinking": { - id: "qwen3-235b-a22b-thinking", - name: "Qwen3 235B A22B Thinking", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-25", - last_updated: "2025-07-25", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.9000000000000004 }, - limit: { context: 262144, output: 81920 }, - }, - "deepseek-v3.2": { - id: "deepseek-v3.2", - name: "DeepSeek V3.2", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-22", - last_updated: "2025-09-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.41 }, - limit: { context: 163840, output: 65536 }, - }, - "grok-3-mini": { - id: "grok-3-mini", - name: "xAI Grok 3 Mini", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2024-06-01", - last_updated: "2024-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.5, cache_read: 0.075 }, - limit: { context: 131072, output: 131072 }, - }, - "claude-3-haiku-20240307": { - id: "claude-3-haiku-20240307", - name: "Anthropic: Claude 3 Haiku", - family: "claude-haiku", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2024-03-07", - last_updated: "2024-03-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1.25, cache_read: 0.03, cache_write: 0.3 }, - limit: { context: 200000, output: 4096 }, - }, - "claude-haiku-4-5-20251001": { - id: "claude-haiku-4-5-20251001", - name: "Anthropic: Claude 4.5 Haiku (20251001)", - family: "claude-haiku", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-10", - release_date: "2025-10-01", - last_updated: "2025-10-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.09999999999999999, cache_write: 1.25 }, - limit: { context: 200000, output: 8192 }, - }, - "kimi-k2-0711": { - id: "kimi-k2-0711", - name: "Kimi K2 (07/11)", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5700000000000001, output: 2.3 }, - limit: { context: 131072, output: 16384 }, - }, - "gpt-5": { - id: "gpt-5", - name: "OpenAI GPT-5", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.12500000000000003 }, - limit: { context: 400000, output: 128000 }, - }, - "o4-mini": { - id: "o4-mini", - name: "OpenAI o4 Mini", - family: "o-mini", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2024-06", - release_date: "2024-06-01", - last_updated: "2024-06-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.275 }, - limit: { context: 200000, output: 100000 }, - }, - "gpt-4.1-mini": { - id: "gpt-4.1-mini", - name: "OpenAI GPT-4.1 Mini", - family: "gpt-mini", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.39999999999999997, output: 1.5999999999999999, cache_read: 0.09999999999999999 }, - limit: { context: 1047576, output: 32768 }, - }, - "llama-3.3-70b-versatile": { - id: "llama-3.3-70b-versatile", - name: "Meta Llama 3.3 70B Versatile", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.59, output: 0.7899999999999999 }, - limit: { context: 131072, output: 32678 }, - }, - "llama-4-maverick": { - id: "llama-4-maverick", - name: "Meta Llama 4 Maverick 17B 128E", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 131072, output: 8192 }, - }, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.48, output: 2 }, - limit: { context: 256000, output: 262144 }, - }, - "gemma2-9b-it": { - id: "gemma2-9b-it", - name: "Google Gemma 2", - family: "gemma", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-06", - release_date: "2024-06-25", - last_updated: "2024-06-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.01, output: 0.03 }, - limit: { context: 8192, output: 8192 }, - }, - "deepseek-tng-r1t2-chimera": { - id: "deepseek-tng-r1t2-chimera", - name: "DeepSeek TNG R1T2 Chimera", - family: "deepseek-thinking", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-02", - last_updated: "2025-07-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 130000, output: 163840 }, - }, - "sonar-pro": { - id: "sonar-pro", - name: "Perplexity Sonar Pro", - family: "sonar-pro", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-27", - last_updated: "2025-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, output: 4096 }, - }, - "claude-opus-4": { - id: "claude-opus-4", - name: "Anthropic: Claude Opus 4", - family: "claude-opus", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-14", - last_updated: "2025-05-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "gpt-5.1-codex": { - id: "gpt-5.1-codex", - name: "OpenAI: GPT-5.1 Codex", - family: "gpt-codex", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.12500000000000003 }, - limit: { context: 400000, output: 128000 }, - }, - "mistral-large-2411": { - id: "mistral-large-2411", - name: "Mistral-Large", - family: "mistral-large", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-07-24", - last_updated: "2024-07-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6 }, - limit: { context: 128000, output: 32768 }, - }, - "claude-4.5-opus": { - id: "claude-4.5-opus", - name: "Anthropic: Claude Opus 4.5", - family: "claude-opus", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-24", - last_updated: "2025-11-24", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "chatgpt-4o-latest": { - id: "chatgpt-4o-latest", - name: "OpenAI ChatGPT-4o", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-08-14", - last_updated: "2024-08-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 20, cache_read: 2.5 }, - limit: { context: 128000, output: 16384 }, - }, - "llama-3.1-8b-instruct": { - id: "llama-3.1-8b-instruct", - name: "Meta Llama 3.1 8B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0.049999999999999996 }, - limit: { context: 16384, output: 16384 }, - }, - "claude-sonnet-4": { - id: "claude-sonnet-4", - name: "Anthropic: Claude Sonnet 4", - family: "claude-sonnet", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-14", - last_updated: "2025-05-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.30000000000000004, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "gemini-3-pro-preview": { - id: "gemini-3-pro-preview", - name: "Google Gemini 3 Pro Preview", - family: "gemini-pro", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "audio", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.19999999999999998 }, - limit: { context: 1048576, output: 65536 }, - }, - "qwen3-next-80b-a3b-instruct": { - id: "qwen3-next-80b-a3b-instruct", - name: "Qwen3 Next 80B A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 1.4 }, - limit: { context: 262000, output: 16384 }, - }, - "llama-prompt-guard-2-86m": { - id: "llama-prompt-guard-2-86m", - name: "Meta Llama Prompt Guard 2 86M", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2024-10-01", - last_updated: "2024-10-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.01, output: 0.01 }, - limit: { context: 512, output: 2 }, - }, - "o3-mini": { - id: "o3-mini", - name: "OpenAI o3 Mini", - family: "o-mini", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2023-10", - release_date: "2023-10-01", - last_updated: "2023-10-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.55 }, - limit: { context: 200000, output: 100000 }, - }, - "gemma-3-12b-it": { - id: "gemma-3-12b-it", - name: "Google Gemma 3 12B", - family: "gemma", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-12", - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.049999999999999996, output: 0.09999999999999999 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-30b-a3b": { - id: "qwen3-30b-a3b", - name: "Qwen3 30B A3B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-06-01", - last_updated: "2025-06-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.08, output: 0.29 }, - limit: { context: 41000, output: 41000 }, - }, - "grok-4-fast-non-reasoning": { - id: "grok-4-fast-non-reasoning", - name: "xAI Grok 4 Fast Non-Reasoning", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-19", - last_updated: "2025-09-19", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.19999999999999998, output: 0.5, cache_read: 0.049999999999999996 }, - limit: { context: 2000000, output: 2000000 }, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - name: "OpenAI GPT-5 Mini", - family: "gpt-mini", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.024999999999999998 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-oss-20b": { - id: "gpt-oss-20b", - name: "OpenAI GPT-OSS 20b", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2024-06-01", - last_updated: "2024-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.049999999999999996, output: 0.19999999999999998 }, - limit: { context: 131072, output: 131072 }, - }, - "hermes-2-pro-llama-3-8b": { - id: "hermes-2-pro-llama-3-8b", - name: "Hermes 2 Pro Llama 3 8B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-05", - release_date: "2024-05-27", - last_updated: "2024-05-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.14 }, - limit: { context: 131072, output: 131072 }, - }, - "gpt-5.1-chat-latest": { - id: "gpt-5.1-chat-latest", - name: "OpenAI GPT-5.1 Chat", - family: "gpt-codex", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.12500000000000003 }, - limit: { context: 128000, output: 16384 }, - }, - "claude-opus-4-1-20250805": { - id: "claude-opus-4-1-20250805", - name: "Anthropic: Claude Opus 4.1 (20250805)", - family: "claude-opus", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - name: "Google Gemini 2.5 Pro", - family: "gemini-pro", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.3125, cache_write: 1.25 }, - limit: { context: 1048576, output: 65536 }, - }, - "gpt-5-nano": { - id: "gpt-5-nano", - name: "OpenAI GPT-5 Nano", - family: "gpt-nano", - attachment: false, - reasoning: false, - tool_call: true, - temperature: false, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.049999999999999996, output: 0.39999999999999997, cache_read: 0.005 }, - limit: { context: 400000, output: 128000 }, - }, - "o1-mini": { - id: "o1-mini", - name: "OpenAI: o1-mini", - family: "o-mini", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2025-01", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.55 }, - limit: { context: 128000, output: 65536 }, - }, - "gpt-4o": { - id: "gpt-4o", - name: "OpenAI GPT-4o", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-05", - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10, cache_read: 1.25 }, - limit: { context: 128000, output: 16384 }, - }, - }, - }, - vercel: { - id: "vercel", - env: ["AI_GATEWAY_API_KEY"], - npm: "@ai-sdk/gateway", - name: "Vercel AI Gateway", - doc: "https://github.com/vercel/ai/tree/5eb85cc45a259553501f535b8ac79a77d0e79223/packages/gateway", - models: { - "prime-intellect/intellect-3": { - id: "prime-intellect/intellect-3", - name: "INTELLECT 3", - family: "intellect", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-11-26", - last_updated: "2025-11-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.1 }, - limit: { context: 131072, output: 131072 }, - }, - "zai/glm-5": { - id: "zai/glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2, cache_read: 0.2 }, - limit: { context: 202800, output: 131072 }, - }, - "zai/glm-4.7-flashx": { - id: "zai/glm-4.7-flashx", - name: "GLM 4.7 FlashX", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01", - last_updated: "2025-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.4, cache_read: 0.01 }, - limit: { context: 200000, output: 128000 }, - }, - "zai/glm-4.5-air": { - id: "zai/glm-4.5-air", - name: "GLM 4.5 Air", - family: "glm-air", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 1.1 }, - limit: { context: 128000, output: 96000 }, - }, - "zai/glm-4.5": { - id: "zai/glm-4.5", - name: "GLM 4.5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 131072, output: 131072 }, - }, - "zai/glm-4.7-flash": { - id: "zai/glm-4.7-flash", - name: "GLM 4.7 Flash", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-13", - last_updated: "2026-03-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.39999999999999997 }, - limit: { context: 200000, output: 131000 }, - }, - "zai/glm-4.6": { - id: "zai/glm-4.6", - name: "GLM 4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.45, output: 1.8 }, - limit: { context: 200000, output: 96000 }, - }, - "zai/glm-4.7": { - id: "zai/glm-4.7", - name: "GLM 4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.43, output: 1.75, cache_read: 0.08 }, - limit: { context: 202752, output: 120000 }, - }, - "zai/glm-4.6v-flash": { - id: "zai/glm-4.6v-flash", - name: "GLM-4.6V-Flash", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 24000 }, - }, - "zai/glm-4.5v": { - id: "zai/glm-4.5v", - name: "GLM 4.5V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-08-11", - last_updated: "2025-08-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 1.8 }, - limit: { context: 66000, output: 66000 }, - }, - "zai/glm-4.6v": { - id: "zai/glm-4.6v", - name: "GLM-4.6V", - family: "glm", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.9, cache_read: 0.05 }, - limit: { context: 128000, output: 24000 }, - }, - "nvidia/nemotron-nano-12b-v2-vl": { - id: "nvidia/nemotron-nano-12b-v2-vl", - name: "Nvidia Nemotron Nano 12B V2 VL", - family: "nemotron", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12", - last_updated: "2024-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 131072, output: 131072 }, - }, - "nvidia/nemotron-nano-9b-v2": { - id: "nvidia/nemotron-nano-9b-v2", - name: "Nvidia Nemotron Nano 9B V2", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-18", - last_updated: "2025-08-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.04, output: 0.16 }, - limit: { context: 131072, output: 131072 }, - }, - "nvidia/nemotron-3-nano-30b-a3b": { - id: "nvidia/nemotron-3-nano-30b-a3b", - name: "Nemotron 3 Nano 30B A3B", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12", - last_updated: "2024-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.06, output: 0.24 }, - limit: { context: 262144, output: 262144 }, - }, - "arcee-ai/trinity-large-preview": { - id: "arcee-ai/trinity-large-preview", - name: "Trinity Large Preview", - family: "trinity", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-01", - last_updated: "2025-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1 }, - limit: { context: 131000, output: 131000 }, - }, - "arcee-ai/trinity-mini": { - id: "arcee-ai/trinity-mini", - name: "Trinity Mini", - family: "trinity", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2025-12", - last_updated: "2025-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.15 }, - limit: { context: 131072, output: 131072 }, - }, - "xiaomi/mimo-v2-flash": { - id: "xiaomi/mimo-v2-flash", - name: "MiMo V2 Flash", - family: "mimo", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.29 }, - limit: { context: 262144, output: 32000 }, - }, - "inception/mercury-2": { - id: "inception/mercury-2", - name: "Mercury 2", - family: "mercury", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-24", - last_updated: "2026-03-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.75, cache_read: 0.024999999999999998 }, - limit: { context: 128000, output: 128000 }, - }, - "inception/mercury-coder-small": { - id: "inception/mercury-coder-small", - name: "Mercury Coder Small Beta", - family: "mercury", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-02-26", - last_updated: "2025-02-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1 }, - limit: { context: 32000, output: 16384 }, - }, - "voyage/voyage-3-large": { - id: "voyage/voyage-3-large", - name: "voyage-3-large", - family: "voyage", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "voyage/voyage-code-3": { - id: "voyage/voyage-code-3", - name: "voyage-code-3", - family: "voyage", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "voyage/voyage-law-2": { - id: "voyage/voyage-law-2", - name: "voyage-law-2", - family: "voyage", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-03", - last_updated: "2024-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.12, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "voyage/voyage-finance-2": { - id: "voyage/voyage-finance-2", - name: "voyage-finance-2", - family: "voyage", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-03", - last_updated: "2024-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.12, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "voyage/voyage-code-2": { - id: "voyage/voyage-code-2", - name: "voyage-code-2", - family: "voyage", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-01", - last_updated: "2024-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.12, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "voyage/voyage-4-lite": { - id: "voyage/voyage-4-lite", - name: "voyage-4-lite", - family: "voyage", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2026-03-06", - last_updated: "2026-03-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 32000, output: 0 }, - }, - "voyage/voyage-3.5-lite": { - id: "voyage/voyage-3.5-lite", - name: "voyage-3.5-lite", - family: "voyage", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-05-20", - last_updated: "2025-05-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "voyage/voyage-4-large": { - id: "voyage/voyage-4-large", - name: "voyage-4-large", - family: "voyage", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2026-03-06", - last_updated: "2026-03-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 32000, output: 0 }, - }, - "voyage/voyage-3.5": { - id: "voyage/voyage-3.5", - name: "voyage-3.5", - family: "voyage", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-05-20", - last_updated: "2025-05-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.06, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "voyage/voyage-4": { - id: "voyage/voyage-4", - name: "voyage-4", - family: "voyage", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2026-03-06", - last_updated: "2026-03-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 32000, output: 0 }, - }, - "amazon/nova-2-lite": { - id: "amazon/nova-2-lite", - name: "Nova 2 Lite", - family: "nova", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5 }, - limit: { context: 1000000, output: 1000000 }, - }, - "amazon/titan-embed-text-v2": { - id: "amazon/titan-embed-text-v2", - name: "Titan Text Embeddings V2", - family: "titan-embed", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-04", - last_updated: "2024-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "amazon/nova-lite": { - id: "amazon/nova-lite", - name: "Nova Lite", - family: "nova-lite", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.06, output: 0.24, cache_read: 0.015 }, - limit: { context: 300000, output: 8192 }, - }, - "amazon/nova-pro": { - id: "amazon/nova-pro", - name: "Nova Pro", - family: "nova-pro", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 3.2, cache_read: 0.2 }, - limit: { context: 300000, output: 8192 }, - }, - "amazon/nova-micro": { - id: "amazon/nova-micro", - name: "Nova Micro", - family: "nova-micro", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.035, output: 0.14, cache_read: 0.00875 }, - limit: { context: 128000, output: 8192 }, - }, - "alibaba/qwen-3-235b": { - id: "alibaba/qwen-3-235b", - name: "Qwen3 235B A22B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.13, output: 0.6 }, - limit: { context: 40960, output: 16384 }, - }, - "alibaba/qwen3-max-preview": { - id: "alibaba/qwen3-max-preview", - name: "Qwen3 Max Preview", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-23", - last_updated: "2025-09-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.2, output: 6, cache_read: 0.24 }, - limit: { context: 262144, output: 32768 }, - }, - "alibaba/qwen3-next-80b-a3b-thinking": { - id: "alibaba/qwen3-next-80b-a3b-thinking", - name: "Qwen3 Next 80B A3B Thinking", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-12", - last_updated: "2025-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 1.5 }, - limit: { context: 131072, output: 65536 }, - }, - "alibaba/qwen3-max-thinking": { - id: "alibaba/qwen3-max-thinking", - name: "Qwen 3 Max Thinking", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-01", - last_updated: "2025-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.2, output: 6, cache_read: 0.24 }, - limit: { context: 256000, output: 65536 }, - }, - "alibaba/qwen3-vl-instruct": { - id: "alibaba/qwen3-vl-instruct", - name: "Qwen3 VL Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-24", - last_updated: "2025-09-24", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 2.8 }, - limit: { context: 131072, output: 129024 }, - }, - "alibaba/qwen3-embedding-8b": { - id: "alibaba/qwen3-embedding-8b", - name: "Qwen3 Embedding 8B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-06-05", - last_updated: "2025-06-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0 }, - limit: { context: 32768, output: 32768 }, - }, - "alibaba/qwen3-coder-next": { - id: "alibaba/qwen3-coder-next", - name: "Qwen3 Coder Next", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-22", - last_updated: "2026-02-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.2 }, - limit: { context: 256000, output: 256000 }, - }, - "alibaba/qwen3-coder": { - id: "alibaba/qwen3-coder", - name: "Qwen3 Coder 480B A35B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.38, output: 1.53 }, - limit: { context: 262144, output: 66536 }, - }, - "alibaba/qwen-3-30b": { - id: "alibaba/qwen-3-30b", - name: "Qwen3-30B-A3B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.08, output: 0.29 }, - limit: { context: 40960, output: 16384 }, - }, - "alibaba/qwen3-embedding-0.6b": { - id: "alibaba/qwen3-embedding-0.6b", - name: "Qwen3 Embedding 0.6B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.01, output: 0 }, - limit: { context: 32768, output: 32768 }, - }, - "alibaba/qwen-3-14b": { - id: "alibaba/qwen-3-14b", - name: "Qwen3-14B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.06, output: 0.24 }, - limit: { context: 40960, output: 16384 }, - }, - "alibaba/qwen3-235b-a22b-thinking": { - id: "alibaba/qwen3-235b-a22b-thinking", - name: "Qwen3 235B A22B Thinking 2507", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.9 }, - limit: { context: 262114, output: 262114 }, - }, - "alibaba/qwen3-vl-thinking": { - id: "alibaba/qwen3-vl-thinking", - name: "Qwen3 VL Thinking", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-24", - last_updated: "2025-09-24", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 8.4 }, - limit: { context: 131072, output: 129024 }, - }, - "alibaba/qwen3.5-flash": { - id: "alibaba/qwen3.5-flash", - name: "Qwen 3.5 Flash", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-24", - last_updated: "2026-02-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.001, cache_write: 0.125 }, - limit: { context: 1000000, output: 64000 }, - }, - "alibaba/qwen3-next-80b-a3b-instruct": { - id: "alibaba/qwen3-next-80b-a3b-instruct", - name: "Qwen3 Next 80B A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-12", - last_updated: "2025-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 1.1 }, - limit: { context: 262144, output: 32768 }, - }, - "alibaba/qwen3.5-plus": { - id: "alibaba/qwen3.5-plus", - name: "Qwen 3.5 Plus", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-16", - last_updated: "2026-02-19", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2.4, cache_read: 0.04, cache_write: 0.5 }, - limit: { context: 1000000, output: 64000 }, - }, - "alibaba/qwen3-max": { - id: "alibaba/qwen3-max", - name: "Qwen3 Max", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-23", - last_updated: "2025-09-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.2, output: 6 }, - limit: { context: 262144, output: 32768 }, - }, - "alibaba/qwen-3-32b": { - id: "alibaba/qwen-3-32b", - name: "Qwen 3.32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 40960, output: 16384 }, - }, - "alibaba/qwen3-coder-plus": { - id: "alibaba/qwen3-coder-plus", - name: "Qwen3 Coder Plus", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 5 }, - limit: { context: 1000000, output: 1000000 }, - }, - "alibaba/qwen3-embedding-4b": { - id: "alibaba/qwen3-embedding-4b", - name: "Qwen3 Embedding 4B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-06-05", - last_updated: "2025-06-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0 }, - limit: { context: 32768, output: 32768 }, - }, - "alibaba/qwen3-coder-30b-a3b": { - id: "alibaba/qwen3-coder-30b-a3b", - name: "Qwen 3 Coder 30B A3B Instruct", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.27 }, - limit: { context: 160000, output: 32768 }, - }, - "bfl/flux-pro-1.0-fill": { - id: "bfl/flux-pro-1.0-fill", - name: "FLUX.1 Fill [pro]", - family: "flux", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-10", - last_updated: "2024-10", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 512, output: 0 }, - }, - "bfl/flux-pro-1.1": { - id: "bfl/flux-pro-1.1", - name: "FLUX1.1 [pro]", - family: "flux", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-10", - last_updated: "2024-10", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 512, output: 0 }, - }, - "bfl/flux-kontext-max": { - id: "bfl/flux-kontext-max", - name: "FLUX.1 Kontext Max", - family: "flux", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-06", - last_updated: "2025-06", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 512, output: 0 }, - }, - "bfl/flux-kontext-pro": { - id: "bfl/flux-kontext-pro", - name: "FLUX.1 Kontext Pro", - family: "flux", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-06", - last_updated: "2025-06", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 512, output: 0 }, - }, - "bfl/flux-pro-1.1-ultra": { - id: "bfl/flux-pro-1.1-ultra", - name: "FLUX1.1 [pro] Ultra", - family: "flux", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-11", - last_updated: "2024-11", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 512, output: 0 }, - }, - "mistral/codestral-embed": { - id: "mistral/codestral-embed", - name: "Codestral Embed", - family: "codestral-embed", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "mistral/devstral-small-2": { - id: "mistral/devstral-small-2", - name: "Devstral Small 2", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-05-07", - last_updated: "2025-05-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 256000 }, - }, - "mistral/devstral-2": { - id: "mistral/devstral-2", - name: "Devstral 2", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-12-09", - last_updated: "2025-12-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 256000 }, - }, - "mistral/mistral-large-3": { - id: "mistral/mistral-large-3", - name: "Mistral Large 3", - family: "mistral-large", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2025-12-02", - last_updated: "2025-12-02", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 256000, output: 256000 }, - }, - "mistral/mistral-embed": { - id: "mistral/mistral-embed", - name: "Mistral Embed", - family: "mistral-embed", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2023-12-11", - last_updated: "2023-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "mistral/ministral-14b": { - id: "mistral/ministral-14b", - name: "Ministral 14B", - family: "ministral", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 256000, output: 256000 }, - }, - "mistral/mistral-nemo": { - id: "mistral/mistral-nemo", - name: "Mistral Nemo", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.04, output: 0.17 }, - limit: { context: 60288, output: 16000 }, - }, - "mistral/mistral-medium": { - id: "mistral/mistral-medium", - name: "Mistral Medium 3.1", - family: "mistral-medium", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-05-07", - last_updated: "2025-05-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 128000, output: 64000 }, - }, - "mistral/devstral-small": { - id: "mistral/devstral-small", - name: "Devstral Small 1.1", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-05-07", - last_updated: "2025-05-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 128000, output: 64000 }, - }, - "mistral/codestral": { - id: "mistral/codestral", - name: "Codestral (latest)", - family: "codestral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-05-29", - last_updated: "2025-01-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 256000, output: 4096 }, - }, - "mistral/mixtral-8x22b-instruct": { - id: "mistral/mixtral-8x22b-instruct", - name: "Mixtral 8x22B", - family: "mixtral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-04-17", - last_updated: "2024-04-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 6 }, - limit: { context: 64000, output: 64000 }, - }, - "mistral/mistral-small": { - id: "mistral/mistral-small", - name: "Mistral Small (latest)", - family: "mistral-small", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2024-09-01", - last_updated: "2024-09-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 128000, output: 16384 }, - }, - "mistral/ministral-8b": { - id: "mistral/ministral-8b", - name: "Ministral 8B (latest)", - family: "ministral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-10-01", - last_updated: "2024-10-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 128000, output: 128000 }, - }, - "mistral/pixtral-large": { - id: "mistral/pixtral-large", - name: "Pixtral Large (latest)", - family: "pixtral", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2024-11-01", - last_updated: "2024-11-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 6 }, - limit: { context: 128000, output: 128000 }, - }, - "mistral/pixtral-12b": { - id: "mistral/pixtral-12b", - name: "Pixtral 12B", - family: "pixtral", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2024-09-01", - last_updated: "2024-09-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 128000, output: 128000 }, - }, - "mistral/magistral-small": { - id: "mistral/magistral-small", - name: "Magistral Small", - family: "magistral-small", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-03-17", - last_updated: "2025-03-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 128000, output: 128000 }, - }, - "mistral/magistral-medium": { - id: "mistral/magistral-medium", - name: "Magistral Medium (latest)", - family: "magistral-medium", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-06", - release_date: "2025-03-17", - last_updated: "2025-03-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 5 }, - limit: { context: 128000, output: 16384 }, - }, - "mistral/ministral-3b": { - id: "mistral/ministral-3b", - name: "Ministral 3B (latest)", - family: "ministral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-10-01", - last_updated: "2024-10-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.04 }, - limit: { context: 128000, output: 128000 }, - }, - "kwaipilot/kat-coder-pro-v1": { - id: "kwaipilot/kat-coder-pro-v1", - name: "KAT-Coder-Pro V1", - family: "kat-coder", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2025-10-24", - last_updated: "2025-10-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 256000, output: 32000 }, - }, - "deepseek/deepseek-v3": { - id: "deepseek/deepseek-v3", - name: "DeepSeek V3 0324", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-12-26", - last_updated: "2024-12-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.77, output: 0.77 }, - limit: { context: 163840, output: 16384 }, - }, - "deepseek/deepseek-v3.1": { - id: "deepseek/deepseek-v3.1", - name: "DeepSeek-V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1 }, - limit: { context: 163840, output: 128000 }, - }, - "deepseek/deepseek-v3.1-terminus": { - id: "deepseek/deepseek-v3.1-terminus", - name: "DeepSeek V3.1 Terminus", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-09-22", - last_updated: "2025-09-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 1 }, - limit: { context: 131072, output: 65536 }, - }, - "deepseek/deepseek-v3.2": { - id: "deepseek/deepseek-v3.2", - name: "DeepSeek V3.2", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.4, cache_read: 0.22 }, - limit: { context: 163842, output: 8000 }, - }, - "deepseek/deepseek-v3.2-thinking": { - id: "deepseek/deepseek-v3.2-thinking", - name: "DeepSeek V3.2 Thinking", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.28, output: 0.42, cache_read: 0.03 }, - limit: { context: 128000, output: 64000 }, - }, - "deepseek/deepseek-v3.2-exp": { - id: "deepseek/deepseek-v3.2-exp", - name: "DeepSeek V3.2 Exp", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.4 }, - limit: { context: 163840, output: 163840 }, - }, - "deepseek/deepseek-r1": { - id: "deepseek/deepseek-r1", - name: "DeepSeek-R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-01-20", - last_updated: "2025-05-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.35, output: 5.4 }, - limit: { context: 128000, output: 32768 }, - }, - "moonshotai/kimi-k2-turbo": { - id: "moonshotai/kimi-k2-turbo", - name: "Kimi K2 Turbo", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.4, output: 10 }, - limit: { context: 256000, output: 16384 }, - }, - "moonshotai/kimi-k2-0905": { - id: "moonshotai/kimi-k2-0905", - name: "Kimi K2 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 2.5 }, - limit: { context: 131072, output: 16384 }, - }, - "moonshotai/kimi-k2.5": { - id: "moonshotai/kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01-26", - last_updated: "2026-01-26", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 1.2 }, - limit: { context: 262144, output: 262144 }, - }, - "moonshotai/kimi-k2-thinking": { - id: "moonshotai/kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.47, output: 2, cache_read: 0.14 }, - limit: { context: 216144, output: 216144 }, - }, - "moonshotai/kimi-k2-thinking-turbo": { - id: "moonshotai/kimi-k2-thinking-turbo", - name: "Kimi K2 Thinking Turbo", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.15, output: 8, cache_read: 0.15 }, - limit: { context: 262114, output: 262114 }, - }, - "moonshotai/kimi-k2": { - id: "moonshotai/kimi-k2", - name: "Kimi K2 Instruct", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-14", - last_updated: "2025-07-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3 }, - limit: { context: 131072, output: 16384 }, - status: "deprecated", - }, - "google/gemini-embedding-001": { - id: "google/gemini-embedding-001", - name: "Gemini Embedding 001", - family: "gemini-embedding", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-05-20", - last_updated: "2025-05-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "google/gemini-2.5-flash-lite-preview-09-2025": { - id: "google/gemini-2.5-flash-lite-preview-09-2025", - name: "Gemini 2.5 Flash Lite Preview 09-25", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.01 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/imagen-4.0-fast-generate-001": { - id: "google/imagen-4.0-fast-generate-001", - name: "Imagen 4 Fast", - family: "imagen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-06", - last_updated: "2025-06", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 480, output: 0 }, - }, - "google/text-embedding-005": { - id: "google/text-embedding-005", - name: "Text Embedding 005", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-08", - last_updated: "2024-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.03, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "google/gemini-2.5-flash-preview-09-2025": { - id: "google/gemini-2.5-flash-preview-09-2025", - name: "Gemini 2.5 Flash Preview 09-25", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.03, cache_write: 0.383 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-3-flash": { - id: "google/gemini-3-flash", - name: "Gemini 3 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 3, cache_read: 0.05 }, - limit: { context: 1000000, output: 64000 }, - }, - "google/imagen-4.0-ultra-generate-001": { - id: "google/imagen-4.0-ultra-generate-001", - name: "Imagen 4 Ultra", - family: "imagen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-05-24", - last_updated: "2025-05-24", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 480, output: 0 }, - }, - "google/gemini-3.1-flash-image-preview": { - id: "google/gemini-3.1-flash-image-preview", - name: "Gemini 3.1 Flash Image Preview (Nano Banana 2)", - family: "gemini", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2026-02-26", - last_updated: "2026-03-06", - modalities: { input: ["text", "image"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 0.5, output: 3 }, - limit: { context: 131072, output: 32768 }, - }, - "google/gemini-3.1-flash-lite-preview": { - id: "google/gemini-3.1-flash-lite-preview", - name: "Gemini 3.1 Flash Lite Preview", - family: "gemini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-03", - last_updated: "2026-03-06", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1.5, cache_read: 0.025, cache_write: 1 }, - limit: { context: 1000000, output: 65000 }, - }, - "google/text-multilingual-embedding-002": { - id: "google/text-multilingual-embedding-002", - name: "Text Multilingual Embedding 002", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-03", - last_updated: "2024-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.03, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "google/gemini-2.5-flash-lite": { - id: "google/gemini-2.5-flash-lite", - name: "Gemini 2.5 Flash Lite", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.01 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-3.1-pro-preview": { - id: "google/gemini-3.1-pro-preview", - name: "Gemini 3.1 Pro Preview", - family: "gemini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-19", - last_updated: "2026-02-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2 }, - limit: { context: 1000000, output: 64000 }, - }, - "google/gemini-2.5-flash-image": { - id: "google/gemini-2.5-flash-image", - name: "Nano Banana (Gemini 2.5 Flash Image)", - family: "gemini-flash", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-03-20", - modalities: { input: ["text"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5 }, - limit: { context: 32768, output: 32768 }, - }, - "google/gemini-3-pro-image": { - id: "google/gemini-3-pro-image", - name: "Nano Banana Pro (Gemini 3 Pro Image)", - family: "gemini-pro", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-03", - release_date: "2025-09", - last_updated: "2025-09", - modalities: { input: ["text"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 2, output: 120 }, - limit: { context: 65536, output: 32768 }, - }, - "google/gemini-2.5-flash-image-preview": { - id: "google/gemini-2.5-flash-image-preview", - name: "Nano Banana Preview (Gemini 2.5 Flash Image Preview)", - family: "gemini-flash", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-03-20", - modalities: { input: ["text"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5 }, - limit: { context: 32768, output: 32768 }, - }, - "google/imagen-4.0-generate-001": { - id: "google/imagen-4.0-generate-001", - name: "Imagen 4", - family: "imagen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 480, output: 0 }, - }, - "google/gemini-3-pro-preview": { - id: "google/gemini-3-pro-preview", - name: "Gemini 3 Pro Preview", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "video", "audio", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, cache_read: 0.2, context_over_200k: { input: 4, output: 18, cache_read: 0.4 } }, - limit: { context: 1000000, output: 64000 }, - }, - "google/gemini-2.0-flash": { - id: "google/gemini-2.0-flash", - name: "Gemini 2.0 Flash", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-06", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 1048576, output: 8192 }, - }, - "google/gemini-2.5-pro": { - id: "google/gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.31 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-2.0-flash-lite": { - id: "google/gemini-2.0-flash-lite", - name: "Gemini 2.0 Flash Lite", - family: "gemini-flash-lite", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-06", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 1048576, output: 8192 }, - }, - "google/gemini-2.5-flash": { - id: "google/gemini-2.5-flash", - name: "Gemini 2.5 Flash", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, cache_read: 0.075, input_audio: 1 }, - limit: { context: 1048576, output: 65536 }, - }, - "meituan/longcat-flash-thinking": { - id: "meituan/longcat-flash-thinking", - name: "LongCat Flash Thinking", - family: "longcat", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-23", - last_updated: "2025-09-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 1.5 }, - limit: { context: 128000, output: 8192 }, - }, - "meituan/longcat-flash-thinking-2601": { - id: "meituan/longcat-flash-thinking-2601", - name: "LongCat Flash Thinking 2601", - family: "longcat", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2026-03-13", - last_updated: "2026-03-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 32768, output: 32768 }, - }, - "meituan/longcat-flash-chat": { - id: "meituan/longcat-flash-chat", - name: "LongCat Flash Chat", - family: "longcat", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-30", - last_updated: "2025-08-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - limit: { context: 128000, output: 8192 }, - }, - "bytedance/seed-1.6": { - id: "bytedance/seed-1.6", - name: "Seed 1.6", - family: "seed", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09", - last_updated: "2025-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.05 }, - limit: { context: 256000, output: 32000 }, - }, - "bytedance/seed-1.8": { - id: "bytedance/seed-1.8", - name: "Seed 1.8", - family: "seed", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-10", - last_updated: "2025-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.05 }, - limit: { context: 256000, output: 64000 }, - }, - "meta/llama-3.1-8b": { - id: "meta/llama-3.1-8b", - name: "Llama 3.1 8B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.03, output: 0.05 }, - limit: { context: 131072, output: 16384 }, - }, - "meta/llama-3.2-11b": { - id: "meta/llama-3.2-11b", - name: "Llama 3.2 11B Vision Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.16, output: 0.16 }, - limit: { context: 128000, output: 8192 }, - }, - "meta/llama-3.1-70b": { - id: "meta/llama-3.1-70b", - name: "Llama 3.1 70B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 0.4 }, - limit: { context: 131072, output: 16384 }, - }, - "meta/llama-3.2-90b": { - id: "meta/llama-3.2-90b", - name: "Llama 3.2 90B Vision Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.72, output: 0.72 }, - limit: { context: 128000, output: 8192 }, - }, - "meta/llama-3.2-1b": { - id: "meta/llama-3.2-1b", - name: "Llama 3.2 1B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-18", - last_updated: "2024-09-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 128000, output: 8192 }, - }, - "meta/llama-3.2-3b": { - id: "meta/llama-3.2-3b", - name: "Llama 3.2 3B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-18", - last_updated: "2024-09-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 128000, output: 8192 }, - }, - "meta/llama-4-maverick": { - id: "meta/llama-4-maverick", - name: "Llama-4-Maverick-17B-128E-Instruct-FP8", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "meta/llama-3.3-70b": { - id: "meta/llama-3.3-70b", - name: "Llama-3.3-70B-Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "meta/llama-4-scout": { - id: "meta/llama-4-scout", - name: "Llama-4-Scout-17B-16E-Instruct-FP8", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 4096 }, - }, - "vercel/v0-1.5-md": { - id: "vercel/v0-1.5-md", - name: "v0-1.5-md", - family: "v0", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-06-09", - last_updated: "2025-06-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 128000, output: 32000 }, - }, - "vercel/v0-1.0-md": { - id: "vercel/v0-1.0-md", - name: "v0-1.0-md", - family: "v0", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 128000, output: 32000 }, - }, - "openai/gpt-5.3-codex": { - id: "openai/gpt-5.3-codex", - name: "GPT 5.3 Codex", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-24", - last_updated: "2026-02-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "openai/gpt-5-pro": { - id: "openai/gpt-5-pro", - name: "GPT-5 pro", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image", "pdf"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 15, output: 120 }, - limit: { context: 400000, input: 128000, output: 272000 }, - }, - "openai/text-embedding-ada-002": { - id: "openai/text-embedding-ada-002", - name: "text-embedding-ada-002", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2022-12-15", - last_updated: "2022-12-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0 }, - limit: { context: 8192, input: 6656, output: 1536 }, - }, - "openai/gpt-4o-mini-search-preview": { - id: "openai/gpt-4o-mini-search-preview", - name: "GPT 4o Mini Search Preview", - family: "gpt-mini", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - knowledge: "2023-09", - release_date: "2025-01", - last_updated: "2025-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, input: 111616, output: 16384 }, - }, - "openai/gpt-5.1-codex-max": { - id: "openai/gpt-5.1-codex-max", - name: "GPT 5.1 Codex Max", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "openai/gpt-5.2-codex": { - id: "openai/gpt-5.2-codex", - name: "GPT-5.2-Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-12", - last_updated: "2025-12", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "openai/o3-deep-research": { - id: "openai/o3-deep-research", - name: "o3-deep-research", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-10", - release_date: "2024-06-26", - last_updated: "2024-06-26", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 40, cache_read: 2.5 }, - limit: { context: 200000, input: 100000, output: 100000 }, - }, - "openai/gpt-5.2-chat": { - id: "openai/gpt-5.2-chat", - name: "GPT-5.2 Chat", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.18 }, - limit: { context: 128000, input: 111616, output: 16384 }, - }, - "openai/gpt-5-chat": { - id: "openai/gpt-5-chat", - name: "GPT-5 Chat", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image", "pdf"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 128000, input: 111616, output: 16384 }, - }, - "openai/text-embedding-3-small": { - id: "openai/text-embedding-3-small", - name: "text-embedding-3-small", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-01-25", - last_updated: "2024-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0 }, - limit: { context: 8192, input: 6656, output: 1536 }, - }, - "openai/text-embedding-3-large": { - id: "openai/text-embedding-3-large", - name: "text-embedding-3-large", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-01-25", - last_updated: "2024-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.13, output: 0 }, - limit: { context: 8192, input: 6656, output: 1536 }, - }, - "openai/gpt-3.5-turbo": { - id: "openai/gpt-3.5-turbo", - name: "GPT-3.5 Turbo", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2021-09", - release_date: "2023-03-01", - last_updated: "2023-03-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 16385, input: 12289, output: 4096 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.5 }, - limit: { context: 131072, output: 131072 }, - }, - "openai/gpt-5.1-codex-mini": { - id: "openai/gpt-5.1-codex-mini", - name: "GPT-5.1 Codex mini", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-05-16", - last_updated: "2025-05-16", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.03 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "openai/gpt-5.2": { - id: "openai/gpt-5.2", - name: "GPT-5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.18 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "openai/o3-pro": { - id: "openai/o3-pro", - name: "o3 Pro", - family: "o-pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-10", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 20, output: 80 }, - limit: { context: 200000, input: 100000, output: 100000 }, - }, - "openai/gpt-5.1-thinking": { - id: "openai/gpt-5.1-thinking", - name: "GPT 5.1 Thinking", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-10", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image", "pdf"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "openai/gpt-5.4": { - id: "openai/gpt-5.4", - name: "GPT 5.4", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-05", - last_updated: "2026-03-06", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 15, cache_read: 0.25 }, - limit: { context: 1050000, input: 922000, output: 128000 }, - }, - "openai/codex-mini": { - id: "openai/codex-mini", - name: "Codex Mini", - family: "gpt-codex-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-05-16", - last_updated: "2025-05-16", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 6, cache_read: 0.38 }, - limit: { context: 200000, input: 100000, output: 100000 }, - }, - "openai/gpt-5.4-pro": { - id: "openai/gpt-5.4-pro", - name: "GPT 5.4 Pro", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-05", - last_updated: "2026-03-06", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 30, output: 180 }, - limit: { context: 1050000, input: 922000, output: 128000 }, - }, - "openai/gpt-5.3-chat": { - id: "openai/gpt-5.3-chat", - name: "GPT-5.3 Chat", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-03", - last_updated: "2026-03-06", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 128000, input: 111616, output: 16384 }, - }, - "openai/gpt-oss-safeguard-20b": { - id: "openai/gpt-oss-safeguard-20b", - name: "gpt-oss-safeguard-20b", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.08, output: 0.3, cache_read: 0.04 }, - limit: { context: 131072, input: 65536, output: 65536 }, - }, - "openai/gpt-5.1-codex": { - id: "openai/gpt-5.1-codex", - name: "GPT-5.1-Codex", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "openai/gpt-5.2-pro": { - id: "openai/gpt-5.2-pro", - name: "GPT 5.2 ", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 21, output: 168 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "GPT OSS 20B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.3 }, - limit: { context: 131072, input: 98304, output: 32768 }, - }, - "openai/gpt-3.5-turbo-instruct": { - id: "openai/gpt-3.5-turbo-instruct", - name: "GPT-3.5 Turbo Instruct", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2021-09", - release_date: "2023-03-01", - last_updated: "2023-03-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 2 }, - limit: { context: 8192, input: 4096, output: 4096 }, - }, - "openai/gpt-5.1-instant": { - id: "openai/gpt-5.1-instant", - name: "GPT-5.1 Instant", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image", "pdf"], output: ["text", "image"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 128000, input: 111616, output: 16384 }, - }, - "openai/gpt-4o": { - id: "openai/gpt-4o", - name: "GPT-4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-05-13", - last_updated: "2024-08-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10, cache_read: 1.25 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5-nano": { - id: "openai/gpt-5-nano", - name: "GPT-5 Nano", - family: "gpt-nano", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4, cache_read: 0.005 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "openai/gpt-5-mini": { - id: "openai/gpt-5-mini", - name: "GPT-5 Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.025 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "openai/o3-mini": { - id: "openai/o3-mini", - name: "o3-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05", - release_date: "2024-12-20", - last_updated: "2025-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.55 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4.1-mini": { - id: "openai/gpt-4.1-mini", - name: "GPT-4.1 mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.6, cache_read: 0.1 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/o4-mini": { - id: "openai/o4-mini", - name: "o4-mini", - family: "o-mini", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.28 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-5": { - id: "openai/gpt-5", - name: "GPT-5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "openai/gpt-4-turbo": { - id: "openai/gpt-4-turbo", - name: "GPT-4 Turbo", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - knowledge: "2023-12", - release_date: "2023-11-06", - last_updated: "2024-04-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 30 }, - limit: { context: 128000, output: 4096 }, - }, - "openai/gpt-4.1": { - id: "openai/gpt-4.1", - name: "GPT-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/gpt-4.1-nano": { - id: "openai/gpt-4.1-nano", - name: "GPT-4.1 nano", - family: "gpt-nano", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.03 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/o3": { - id: "openai/o3", - name: "o3", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/o1": { - id: "openai/o1", - name: "o1", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2023-09", - release_date: "2024-12-05", - last_updated: "2024-12-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 60, cache_read: 7.5 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4o-mini": { - id: "openai/gpt-4o-mini", - name: "GPT-4o mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.08 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5-codex": { - id: "openai/gpt-5-codex", - name: "GPT-5-Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "morph/morph-v3-large": { - id: "morph/morph-v3-large", - name: "Morph v3 Large", - family: "morph", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-08-15", - last_updated: "2024-08-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.9, output: 1.9 }, - limit: { context: 32000, output: 32000 }, - }, - "morph/morph-v3-fast": { - id: "morph/morph-v3-fast", - name: "Morph v3 Fast", - family: "morph", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-08-15", - last_updated: "2024-08-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 1.2 }, - limit: { context: 16000, output: 16000 }, - }, - "cohere/embed-v4.0": { - id: "cohere/embed-v4.0", - name: "Embed v4.0", - family: "cohere-embed", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.12, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "cohere/command-a": { - id: "cohere/command-a", - name: "Command A", - family: "command", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-03-13", - last_updated: "2025-03-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 256000, output: 8000 }, - }, - "minimax/minimax-m2.1-lightning": { - id: "minimax/minimax-m2.1-lightning", - name: "MiniMax M2.1 Lightning", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-10-27", - last_updated: "2025-10-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.4, cache_read: 0.03, cache_write: 0.38 }, - limit: { context: 204800, output: 131072 }, - }, - "minimax/minimax-m2.5-highspeed": { - id: "minimax/minimax-m2.5-highspeed", - name: "MiniMax M2.5 High Speed", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-03-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 2.4, cache_read: 0.03, cache_write: 0.375 }, - limit: { context: 0, output: 0 }, - }, - "minimax/minimax-m2.1": { - id: "minimax/minimax-m2.1", - name: "MiniMax M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-10-27", - last_updated: "2025-10-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2, cache_read: 0.03, cache_write: 0.38 }, - limit: { context: 204800, output: 131072 }, - }, - "minimax/minimax-m2": { - id: "minimax/minimax-m2", - name: "MiniMax M2", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-10-27", - last_updated: "2025-10-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 1.15, cache_read: 0.03, cache_write: 0.38 }, - limit: { context: 262114, output: 262114 }, - }, - "minimax/minimax-m2.5": { - id: "minimax/minimax-m2.5", - name: "MiniMax M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2, cache_read: 0.03, cache_write: 0.375 }, - limit: { context: 204800, output: 131000 }, - }, - "recraft/recraft-v2": { - id: "recraft/recraft-v2", - name: "Recraft V2", - family: "recraft", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-03", - last_updated: "2024-03", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 512, output: 0 }, - }, - "recraft/recraft-v3": { - id: "recraft/recraft-v3", - name: "Recraft V3", - family: "recraft", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-10", - last_updated: "2024-10", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 512, output: 0 }, - }, - "perplexity/sonar-reasoning-pro": { - id: "perplexity/sonar-reasoning-pro", - name: "Sonar Reasoning Pro", - family: "sonar-reasoning", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-09", - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8 }, - limit: { context: 127000, output: 8000 }, - }, - "perplexity/sonar": { - id: "perplexity/sonar", - name: "Sonar", - family: "sonar", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-02", - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 1 }, - limit: { context: 127000, output: 8000 }, - }, - "perplexity/sonar-reasoning": { - id: "perplexity/sonar-reasoning", - name: "Sonar Reasoning", - family: "sonar-reasoning", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-09", - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5 }, - limit: { context: 127000, output: 8000 }, - }, - "perplexity/sonar-pro": { - id: "perplexity/sonar-pro", - name: "Sonar Pro", - family: "sonar-pro", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-09", - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, output: 8000 }, - }, - "anthropic/claude-sonnet-4.6": { - id: "anthropic/claude-sonnet-4.6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2025-08", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 1000000, output: 128000 }, - }, - "anthropic/claude-haiku-4.5": { - id: "anthropic/claude-haiku-4.5", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-opus-4.5": { - id: "anthropic/claude-opus-4.5", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-11-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 18.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-3.5-sonnet-20240620": { - id: "anthropic/claude-3.5-sonnet-20240620", - name: "Claude 3.5 Sonnet (2024-06-20)", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-06-20", - last_updated: "2024-06-20", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, output: 8192 }, - }, - "anthropic/claude-opus-4.6": { - id: "anthropic/claude-opus-4.6", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02", - last_updated: "2026-02", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 1000000, output: 128000 }, - }, - "anthropic/claude-sonnet-4.5": { - id: "anthropic/claude-sonnet-4.5", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-sonnet-4": { - id: "anthropic/claude-sonnet-4", - name: "Claude Sonnet 4", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-3-opus": { - id: "anthropic/claude-3-opus", - name: "Claude Opus 3", - family: "claude-opus", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-08-31", - release_date: "2024-02-29", - last_updated: "2024-02-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 4096 }, - }, - "anthropic/claude-opus-4": { - id: "anthropic/claude-opus-4", - name: "Claude Opus 4", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "anthropic/claude-3.5-haiku": { - id: "anthropic/claude-3.5-haiku", - name: "Claude Haiku 3.5", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07-31", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 }, - limit: { context: 200000, output: 8192 }, - }, - "anthropic/claude-3-haiku": { - id: "anthropic/claude-3-haiku", - name: "Claude Haiku 3", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-08-31", - release_date: "2024-03-13", - last_updated: "2024-03-13", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1.25, cache_read: 0.03, cache_write: 0.3 }, - limit: { context: 200000, output: 4096 }, - }, - "anthropic/claude-opus-4.1": { - id: "anthropic/claude-opus-4.1", - name: "Claude Opus 4", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "anthropic/claude-3.7-sonnet": { - id: "anthropic/claude-3.7-sonnet", - name: "Claude Sonnet 3.7", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10-31", - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-3.5-sonnet": { - id: "anthropic/claude-3.5-sonnet", - name: "Claude Sonnet 3.5 v2", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04-30", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 8192 }, - }, - "xai/grok-4-fast-reasoning": { - id: "xai/grok-4-fast-reasoning", - name: "Grok 4 Fast Reasoning", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 256000 }, - }, - "xai/grok-4.20-non-reasoning-beta": { - id: "xai/grok-4.20-non-reasoning-beta", - name: "Grok 4.20 Beta Non-Reasoning", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2026-03-11", - last_updated: "2026-03-13", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6, cache_read: 0.19999999999999998 }, - limit: { context: 2000000, output: 2000000 }, - }, - "xai/grok-imagine-image": { - id: "xai/grok-imagine-image", - name: "Grok Imagine Image", - family: "grok", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2026-01-28", - last_updated: "2026-02-19", - modalities: { input: ["text"], output: ["text", "image"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "xai/grok-4.1-fast-reasoning": { - id: "xai/grok-4.1-fast-reasoning", - name: "Grok 4.1 Fast Reasoning", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "xai/grok-4.1-fast-non-reasoning": { - id: "xai/grok-4.1-fast-non-reasoning", - name: "Grok 4.1 Fast Non-Reasoning", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "xai/grok-4.20-reasoning-beta": { - id: "xai/grok-4.20-reasoning-beta", - name: "Grok 4.20 Beta Reasoning", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-11", - last_updated: "2026-03-13", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6, cache_read: 0.19999999999999998 }, - limit: { context: 2000000, output: 2000000 }, - }, - "xai/grok-imagine-image-pro": { - id: "xai/grok-imagine-image-pro", - name: "Grok Imagine Image Pro", - family: "grok", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2026-01-28", - last_updated: "2026-02-19", - modalities: { input: ["text"], output: ["text", "image"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "xai/grok-4.20-multi-agent-beta": { - id: "xai/grok-4.20-multi-agent-beta", - name: "Grok 4.20 Multi Agent Beta", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-11", - last_updated: "2026-03-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6, cache_read: 0.19999999999999998 }, - limit: { context: 2000000, output: 2000000 }, - }, - "xai/grok-3-fast": { - id: "xai/grok-3-fast", - name: "Grok 3 Fast", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 1.25 }, - limit: { context: 131072, output: 8192 }, - }, - "xai/grok-4-fast-non-reasoning": { - id: "xai/grok-4-fast-non-reasoning", - name: "Grok 4 Fast (Non-Reasoning)", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-09-19", - last_updated: "2025-09-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "xai/grok-3-mini": { - id: "xai/grok-3-mini", - name: "Grok 3 Mini", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.5, reasoning: 0.5, cache_read: 0.075 }, - limit: { context: 131072, output: 8192 }, - }, - "xai/grok-4": { - id: "xai/grok-4", - name: "Grok 4", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, reasoning: 15, cache_read: 0.75 }, - limit: { context: 256000, output: 64000 }, - }, - "xai/grok-3-mini-fast": { - id: "xai/grok-3-mini-fast", - name: "Grok 3 Mini Fast", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 4, reasoning: 4, cache_read: 0.15 }, - limit: { context: 131072, output: 8192 }, - }, - "xai/grok-code-fast-1": { - id: "xai/grok-code-fast-1", - name: "Grok Code Fast 1", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2025-08-28", - last_updated: "2025-08-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.5, cache_read: 0.02 }, - limit: { context: 256000, output: 10000 }, - }, - "xai/grok-3": { - id: "xai/grok-3", - name: "Grok 3", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75 }, - limit: { context: 131072, output: 8192 }, - }, - "xai/grok-2-vision": { - id: "xai/grok-2-vision", - name: "Grok 2 Vision", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-08-20", - last_updated: "2024-08-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 10, cache_read: 2 }, - limit: { context: 8192, output: 4096 }, - }, - }, - }, - openai: { - id: "openai", - env: ["OPENAI_API_KEY"], - npm: "@ai-sdk/openai", - name: "OpenAI", - doc: "https://platform.openai.com/docs/models", - models: { - "gpt-4o-2024-11-20": { - id: "gpt-4o-2024-11-20", - name: "GPT-4o (2024-11-20)", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-11-20", - last_updated: "2024-11-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10, cache_read: 1.25 }, - limit: { context: 128000, output: 16384 }, - }, - "gpt-5.3-codex": { - id: "gpt-5.3-codex", - name: "GPT-5.3 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "gpt-5-codex": { - id: "gpt-5-codex", - name: "GPT-5-Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "gpt-5-pro": { - id: "gpt-5-pro", - name: "GPT-5 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-10-06", - last_updated: "2025-10-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 120 }, - limit: { context: 400000, input: 272000, output: 272000 }, - }, - "gpt-4o-mini": { - id: "gpt-4o-mini", - name: "GPT-4o mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.08 }, - limit: { context: 128000, output: 16384 }, - }, - "text-embedding-ada-002": { - id: "text-embedding-ada-002", - name: "text-embedding-ada-002", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2022-12", - release_date: "2022-12-15", - last_updated: "2022-12-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "gpt-5-chat-latest": { - id: "gpt-5-chat-latest", - name: "GPT-5 Chat (latest)", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: true, - temperature: true, - knowledge: "2024-09-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "codex-mini-latest": { - id: "codex-mini-latest", - name: "Codex Mini", - family: "gpt-codex-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-04", - release_date: "2025-05-16", - last_updated: "2025-05-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 6, cache_read: 0.375 }, - limit: { context: 200000, output: 100000 }, - }, - "gpt-5.1-codex-max": { - id: "gpt-5.1-codex-max", - name: "GPT-5.1 Codex Max", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "gpt-4o-2024-05-13": { - id: "gpt-4o-2024-05-13", - name: "GPT-4o (2024-05-13)", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 15 }, - limit: { context: 128000, output: 4096 }, - }, - "gpt-5.2-chat-latest": { - id: "gpt-5.2-chat-latest", - name: "GPT-5.2 Chat", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 128000, output: 16384 }, - }, - "gpt-5.2-codex": { - id: "gpt-5.2-codex", - name: "GPT-5.2 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "o3-deep-research": { - id: "o3-deep-research", - name: "o3-deep-research", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05", - release_date: "2024-06-26", - last_updated: "2024-06-26", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 40, cache_read: 2.5 }, - limit: { context: 200000, output: 100000 }, - }, - o1: { - id: "o1", - name: "o1", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2023-09", - release_date: "2024-12-05", - last_updated: "2024-12-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 60, cache_read: 7.5 }, - limit: { context: 200000, output: 100000 }, - }, - "gpt-5.1": { - id: "gpt-5.1", - name: "GPT-5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "o4-mini-deep-research": { - id: "o4-mini-deep-research", - name: "o4-mini-deep-research", - family: "o-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05", - release_date: "2024-06-26", - last_updated: "2024-06-26", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 200000, output: 100000 }, - }, - "gpt-5.3-codex-spark": { - id: "gpt-5.3-codex-spark", - name: "GPT-5.3 Codex Spark", - family: "gpt-codex-spark", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 128000, input: 100000, output: 32000 }, - }, - o3: { - id: "o3", - name: "o3", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 200000, output: 100000 }, - }, - "text-embedding-3-small": { - id: "text-embedding-3-small", - name: "text-embedding-3-small", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2024-01", - release_date: "2024-01-25", - last_updated: "2024-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0 }, - limit: { context: 8191, output: 1536 }, - }, - "gpt-4.1-nano": { - id: "gpt-4.1-nano", - name: "GPT-4.1 nano", - family: "gpt-nano", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.03 }, - limit: { context: 1047576, output: 32768 }, - }, - "text-embedding-3-large": { - id: "text-embedding-3-large", - name: "text-embedding-3-large", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2024-01", - release_date: "2024-01-25", - last_updated: "2024-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.13, output: 0 }, - limit: { context: 8191, output: 3072 }, - }, - "gpt-3.5-turbo": { - id: "gpt-3.5-turbo", - name: "GPT-3.5-turbo", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - knowledge: "2021-09-01", - release_date: "2023-03-01", - last_updated: "2023-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.5, cache_read: 1.25 }, - limit: { context: 16385, output: 4096 }, - }, - "gpt-5.1-codex-mini": { - id: "gpt-5.1-codex-mini", - name: "GPT-5.1 Codex mini", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.025 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "gpt-5.2": { - id: "gpt-5.2", - name: "GPT-5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "gpt-4.1": { - id: "gpt-4.1", - name: "GPT-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 1047576, output: 32768 }, - }, - "o3-pro": { - id: "o3-pro", - name: "o3-pro", - family: "o-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-06-10", - last_updated: "2025-06-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 20, output: 80 }, - limit: { context: 200000, output: 100000 }, - }, - "gpt-4-turbo": { - id: "gpt-4-turbo", - name: "GPT-4 Turbo", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - knowledge: "2023-12", - release_date: "2023-11-06", - last_updated: "2024-04-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 30 }, - limit: { context: 128000, output: 4096 }, - }, - "gpt-5": { - id: "gpt-5", - name: "GPT-5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "o4-mini": { - id: "o4-mini", - name: "o4-mini", - family: "o-mini", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.28 }, - limit: { context: 200000, output: 100000 }, - }, - "gpt-4.1-mini": { - id: "gpt-4.1-mini", - name: "GPT-4.1 mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.6, cache_read: 0.1 }, - limit: { context: 1047576, output: 32768 }, - }, - "gpt-5.4": { - id: "gpt-5.4", - name: "GPT-5.4", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 2.5, - output: 15, - cache_read: 0.25, - context_over_200k: { input: 5, output: 22.5, cache_read: 0.5 }, - }, - limit: { context: 1050000, input: 922000, output: 128000 }, - }, - "o1-preview": { - id: "o1-preview", - name: "o1-preview", - family: "o", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2023-09", - release_date: "2024-09-12", - last_updated: "2024-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 60, cache_read: 7.5 }, - limit: { context: 128000, output: 32768 }, - }, - "gpt-5.4-pro": { - id: "gpt-5.4-pro", - name: "GPT-5.4 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 30, output: 180, context_over_200k: { input: 60, output: 270 } }, - limit: { context: 1050000, input: 922000, output: 128000 }, - }, - "o1-pro": { - id: "o1-pro", - name: "o1-pro", - family: "o-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2023-09", - release_date: "2025-03-19", - last_updated: "2025-03-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 150, output: 600 }, - limit: { context: 200000, output: 100000 }, - }, - "gpt-5.1-codex": { - id: "gpt-5.1-codex", - name: "GPT-5.1 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "gpt-5.2-pro": { - id: "gpt-5.2-pro", - name: "GPT-5.2 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 21, output: 168 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "o3-mini": { - id: "o3-mini", - name: "o3-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05", - release_date: "2024-12-20", - last_updated: "2025-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.55 }, - limit: { context: 200000, output: 100000 }, - }, - "gpt-4o-2024-08-06": { - id: "gpt-4o-2024-08-06", - name: "GPT-4o (2024-08-06)", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-08-06", - last_updated: "2024-08-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10, cache_read: 1.25 }, - limit: { context: 128000, output: 16384 }, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - name: "GPT-5 Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.025 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "gpt-5.1-chat-latest": { - id: "gpt-5.1-chat-latest", - name: "GPT-5.1 Chat", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 128000, output: 16384 }, - }, - "gpt-4": { - id: "gpt-4", - name: "GPT-4", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - knowledge: "2023-11", - release_date: "2023-11-06", - last_updated: "2024-04-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 30, output: 60 }, - limit: { context: 8192, output: 8192 }, - }, - "gpt-5-nano": { - id: "gpt-5-nano", - name: "GPT-5 Nano", - family: "gpt-nano", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4, cache_read: 0.005 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "o1-mini": { - id: "o1-mini", - name: "o1-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: true, - temperature: false, - knowledge: "2023-09", - release_date: "2024-09-12", - last_updated: "2024-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.55 }, - limit: { context: 128000, output: 65536 }, - }, - "gpt-4o": { - id: "gpt-4o", - name: "GPT-4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-05-13", - last_updated: "2024-08-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10, cache_read: 1.25 }, - limit: { context: 128000, output: 16384 }, - }, - }, - }, - moark: { - id: "moark", - env: ["MOARK_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://moark.com/v1", - name: "Moark", - doc: "https://moark.com/docs/openapi/v1#tag/%E6%96%87%E6%9C%AC%E7%94%9F%E6%88%90", - models: { - "GLM-4.7": { - id: "GLM-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 3.5, output: 14 }, - limit: { context: 204800, output: 131072 }, - }, - "MiniMax-M2.1": { - id: "MiniMax-M2.1", - name: "MiniMax-M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.1, output: 8.4 }, - limit: { context: 204800, output: 131072 }, - }, - }, - }, - morph: { - id: "morph", - env: ["MORPH_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.morphllm.com/v1", - name: "Morph", - doc: "https://docs.morphllm.com/api-reference/introduction", - models: { - auto: { - id: "auto", - name: "Auto", - family: "auto", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-06-01", - last_updated: "2024-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.85, output: 1.55 }, - limit: { context: 32000, output: 32000 }, - }, - "morph-v3-fast": { - id: "morph-v3-fast", - name: "Morph v3 Fast", - family: "morph", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-08-15", - last_updated: "2024-08-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 1.2 }, - limit: { context: 16000, output: 16000 }, - }, - "morph-v3-large": { - id: "morph-v3-large", - name: "Morph v3 Large", - family: "morph", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2024-08-15", - last_updated: "2024-08-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.9, output: 1.9 }, - limit: { context: 32000, output: 32000 }, - }, - }, - }, - cohere: { - id: "cohere", - env: ["COHERE_API_KEY"], - npm: "@ai-sdk/cohere", - name: "Cohere", - doc: "https://docs.cohere.com/docs/models", - models: { - "c4ai-aya-expanse-32b": { - id: "c4ai-aya-expanse-32b", - name: "Aya Expanse 32B", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-10-24", - last_updated: "2024-10-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 128000, output: 4000 }, - }, - "command-a-03-2025": { - id: "command-a-03-2025", - name: "Command A", - family: "command-a", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06-01", - release_date: "2025-03-13", - last_updated: "2025-03-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.5, output: 10 }, - limit: { context: 256000, output: 8000 }, - }, - "command-r7b-arabic-02-2025": { - id: "command-r7b-arabic-02-2025", - name: "Command R7B Arabic", - family: "command-r", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-06-01", - release_date: "2025-02-27", - last_updated: "2025-02-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.0375, output: 0.15 }, - limit: { context: 128000, output: 4000 }, - }, - "command-a-translate-08-2025": { - id: "command-a-translate-08-2025", - name: "Command A Translate", - family: "command-a", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-06-01", - release_date: "2025-08-28", - last_updated: "2025-08-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.5, output: 10 }, - limit: { context: 8000, output: 8000 }, - }, - "command-r-08-2024": { - id: "command-r-08-2024", - name: "Command R", - family: "command-r", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06-01", - release_date: "2024-08-30", - last_updated: "2024-08-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 4000 }, - }, - "command-r-plus-08-2024": { - id: "command-r-plus-08-2024", - name: "Command R+", - family: "command-r", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06-01", - release_date: "2024-08-30", - last_updated: "2024-08-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.5, output: 10 }, - limit: { context: 128000, output: 4000 }, - }, - "command-a-reasoning-08-2025": { - id: "command-a-reasoning-08-2025", - name: "Command A Reasoning", - family: "command-a", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06-01", - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.5, output: 10 }, - limit: { context: 256000, output: 32000 }, - }, - "c4ai-aya-expanse-8b": { - id: "c4ai-aya-expanse-8b", - name: "Aya Expanse 8B", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-10-24", - last_updated: "2024-10-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 8000, output: 4000 }, - }, - "c4ai-aya-vision-8b": { - id: "c4ai-aya-vision-8b", - name: "Aya Vision 8B", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-03-04", - last_updated: "2025-05-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 16000, output: 4000 }, - }, - "c4ai-aya-vision-32b": { - id: "c4ai-aya-vision-32b", - name: "Aya Vision 32B", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-03-04", - last_updated: "2025-05-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - limit: { context: 16000, output: 4000 }, - }, - "command-r7b-12-2024": { - id: "command-r7b-12-2024", - name: "Command R7B", - family: "command-r", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-06-01", - release_date: "2024-02-27", - last_updated: "2024-02-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.0375, output: 0.15 }, - limit: { context: 128000, output: 4000 }, - }, - "command-a-vision-07-2025": { - id: "command-a-vision-07-2025", - name: "Command A Vision", - family: "command-a", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-06-01", - release_date: "2025-07-31", - last_updated: "2025-07-31", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 2.5, output: 10 }, - limit: { context: 128000, output: 8000 }, - }, - }, - }, - v0: { - id: "v0", - env: ["V0_API_KEY"], - npm: "@ai-sdk/vercel", - name: "v0", - doc: "https://sdk.vercel.ai/providers/ai-sdk-providers/vercel", - models: { - "v0-1.0-md": { - id: "v0-1.0-md", - name: "v0-1.0-md", - family: "v0", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 128000, output: 32000 }, - }, - "v0-1.5-md": { - id: "v0-1.5-md", - name: "v0-1.5-md", - family: "v0", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-06-09", - last_updated: "2025-06-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 128000, output: 32000 }, - }, - "v0-1.5-lg": { - id: "v0-1.5-lg", - name: "v0-1.5-lg", - family: "v0", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-06-09", - last_updated: "2025-06-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75 }, - limit: { context: 512000, output: 32000 }, - }, - }, - }, - minimax: { - id: "minimax", - env: ["MINIMAX_API_KEY"], - npm: "@ai-sdk/anthropic", - api: "https://api.minimax.io/anthropic/v1", - name: "MiniMax (minimax.io)", - doc: "https://platform.minimax.io/docs/guides/quickstart", - models: { - "MiniMax-M2.5": { - id: "MiniMax-M2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.03, cache_write: 0.375 }, - limit: { context: 204800, output: 131072 }, - }, - "MiniMax-M2": { - id: "MiniMax-M2", - name: "MiniMax-M2", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-10-27", - last_updated: "2025-10-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 196608, output: 128000 }, - }, - "MiniMax-M2.5-highspeed": { - id: "MiniMax-M2.5-highspeed", - name: "MiniMax-M2.5-highspeed", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-13", - last_updated: "2026-02-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.4, cache_read: 0.06, cache_write: 0.375 }, - limit: { context: 204800, output: 131072 }, - }, - "MiniMax-M2.1": { - id: "MiniMax-M2.1", - name: "MiniMax-M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 204800, output: 131072 }, - }, - }, - }, - vultr: { - id: "vultr", - env: ["VULTR_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.vultrinference.com/v1", - name: "Vultr", - doc: "https://api.vultrinference.com/", - models: { - "kimi-k2-instruct": { - id: "kimi-k2-instruct", - name: "Kimi K2 Instruct", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 58904, output: 4096 }, - }, - "qwen2.5-coder-32b-instruct": { - id: "qwen2.5-coder-32b-instruct", - name: "Qwen2.5 Coder 32B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2024-11-06", - last_updated: "2024-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 12952, output: 2048 }, - }, - "gpt-oss-120b": { - id: "gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-06-23", - last_updated: "2025-06-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 121808, output: 8192 }, - }, - "deepseek-r1-distill-llama-70b": { - id: "deepseek-r1-distill-llama-70b", - name: "DeepSeek R1 Distill Llama 70B", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 121808, output: 8192 }, - }, - "deepseek-r1-distill-qwen-32b": { - id: "deepseek-r1-distill-qwen-32b", - name: "DeepSeek R1 Distill Qwen 32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 121808, output: 8192 }, - }, - }, - }, - baseten: { - id: "baseten", - env: ["BASETEN_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://inference.baseten.co/v1", - name: "Baseten", - doc: "https://docs.baseten.co/development/model-apis/overview", - models: { - "zai-org/GLM-4.6": { - id: "zai-org/GLM-4.6", - name: "GLM 4.6", - family: "glm", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-08-31", - release_date: "2025-09-16", - last_updated: "2025-09-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 200000, output: 200000 }, - }, - "zai-org/GLM-4.7": { - id: "zai-org/GLM-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 204800, output: 131072 }, - }, - "zai-org/GLM-5": { - id: "zai-org/GLM-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2026-01", - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.95, output: 3.15 }, - limit: { context: 202752, output: 131072 }, - }, - "nvidia/Nemotron-3-Super": { - id: "nvidia/Nemotron-3-Super", - name: "Nemotron 3 Super", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2026-02", - release_date: "2026-03-11", - last_updated: "2026-03-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.75 }, - limit: { context: 262144, output: 32678 }, - }, - "MiniMaxAI/MiniMax-M2.5": { - id: "MiniMaxAI/MiniMax-M2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2026-01", - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 204000, output: 204000 }, - }, - "deepseek-ai/DeepSeek-V3.1": { - id: "deepseek-ai/DeepSeek-V3.1", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-25", - last_updated: "2025-08-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 164000, output: 131000 }, - }, - "deepseek-ai/DeepSeek-V3.2": { - id: "deepseek-ai/DeepSeek-V3.2", - name: "DeepSeek V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-10", - release_date: "2025-12-01", - last_updated: "2026-03-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.45 }, - limit: { context: 163800, output: 131100 }, - status: "deprecated", - }, - "deepseek-ai/DeepSeek-V3-0324": { - id: "deepseek-ai/DeepSeek-V3-0324", - name: "DeepSeek V3 0324", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-03-24", - last_updated: "2025-03-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.77, output: 0.77 }, - limit: { context: 164000, output: 131000 }, - }, - "moonshotai/Kimi-K2-Instruct-0905": { - id: "moonshotai/Kimi-K2-Instruct-0905", - name: "Kimi K2 Instruct 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-09-05", - last_updated: "2026-03-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5 }, - limit: { context: 262144, output: 262144 }, - status: "deprecated", - }, - "moonshotai/Kimi-K2.5": { - id: "moonshotai/Kimi-K2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-12", - release_date: "2026-01-30", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3 }, - limit: { context: 262144, output: 8192 }, - }, - "moonshotai/Kimi-K2-Thinking": { - id: "moonshotai/Kimi-K2-Thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-08", - release_date: "2025-11-06", - last_updated: "2026-03-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5 }, - limit: { context: 262144, output: 262144 }, - status: "deprecated", - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.5 }, - limit: { context: 128000, output: 128000 }, - }, - }, - }, - jiekou: { - id: "jiekou", - env: ["JIEKOU_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.jiekou.ai/openai", - name: "Jiekou.AI", - doc: "https://docs.jiekou.ai/docs/support/quickstart?utm_source=github_models.dev", - models: { - "gpt-5-codex": { - id: "gpt-5-codex", - name: "gpt-5-codex", - family: "gpt-codex", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.125, output: 9 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-5-pro": { - id: "gpt-5-pro", - name: "gpt-5-pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 13.5, output: 108 }, - limit: { context: 400000, output: 272000 }, - }, - "claude-opus-4-5-20251101": { - id: "claude-opus-4-5-20251101", - name: "claude-opus-4-5-20251101", - family: "claude-opus", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 4.5, output: 22.5 }, - limit: { context: 200000, output: 65536 }, - }, - "grok-4-fast-reasoning": { - id: "grok-4-fast-reasoning", - name: "grok-4-fast-reasoning", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0.45 }, - limit: { context: 2000000, output: 2000000 }, - }, - "gemini-2.5-flash-lite-preview-09-2025": { - id: "gemini-2.5-flash-lite-preview-09-2025", - name: "gemini-2.5-flash-lite-preview-09-2025", - family: "gemini-flash-lite", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09, output: 0.36 }, - limit: { context: 1048576, output: 65536 }, - }, - "gpt-5-chat-latest": { - id: "gpt-5-chat-latest", - name: "gpt-5-chat-latest", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.125, output: 9 }, - limit: { context: 400000, output: 128000 }, - }, - "gemini-2.5-pro-preview-06-05": { - id: "gemini-2.5-pro-preview-06-05", - name: "gemini-2.5-pro-preview-06-05", - family: "gemini-pro", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 1.125, output: 9 }, - limit: { context: 1048576, output: 200000 }, - }, - "gpt-5.1-codex-max": { - id: "gpt-5.1-codex-max", - name: "gpt-5.1-codex-max", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.125, output: 9 }, - limit: { context: 400000, output: 128000 }, - }, - "grok-4-0709": { - id: "grok-4-0709", - name: "grok-4-0709", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.7, output: 13.5 }, - limit: { context: 256000, output: 8192 }, - }, - "gpt-5.2-codex": { - id: "gpt-5.2-codex", - name: "gpt-5.2-codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 400000, output: 128000 }, - }, - "claude-opus-4-6": { - id: "claude-opus-4-6", - name: "claude-opus-4-6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02", - last_updated: "2026-02", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25 }, - limit: { context: 1000000, output: 128000 }, - }, - "grok-code-fast-1": { - id: "grok-code-fast-1", - name: "grok-code-fast-1", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 1.35 }, - limit: { context: 256000, output: 256000 }, - }, - "gemini-2.5-flash-preview-05-20": { - id: "gemini-2.5-flash-preview-05-20", - name: "gemini-2.5-flash-preview-05-20", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.135, output: 3.15 }, - limit: { context: 1048576, output: 200000 }, - }, - "grok-4-1-fast-reasoning": { - id: "grok-4-1-fast-reasoning", - name: "grok-4-1-fast-reasoning", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0.45 }, - limit: { context: 2000000, output: 2000000 }, - }, - "gemini-2.5-flash": { - id: "gemini-2.5-flash", - name: "gemini-2.5-flash", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 2.25 }, - limit: { context: 1048576, output: 65535 }, - }, - "grok-4-1-fast-non-reasoning": { - id: "grok-4-1-fast-non-reasoning", - name: "grok-4-1-fast-non-reasoning", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0.45 }, - limit: { context: 2000000, output: 2000000 }, - }, - "gpt-5.1": { - id: "gpt-5.1", - name: "gpt-5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02", - last_updated: "2026-02", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.125, output: 9 }, - limit: { context: 400000, output: 128000 }, - }, - o3: { - id: "o3", - name: "o3", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 40 }, - limit: { context: 131072, output: 131072 }, - }, - "gemini-3-flash-preview": { - id: "gemini-3-flash-preview", - name: "gemini-3-flash-preview", - family: "gemini-flash", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 3 }, - limit: { context: 1048576, output: 65536 }, - }, - "claude-opus-4-20250514": { - id: "claude-opus-4-20250514", - name: "claude-opus-4-20250514", - family: "claude-opus", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 13.5, output: 67.5 }, - limit: { context: 200000, output: 32000 }, - }, - "claude-sonnet-4-5-20250929": { - id: "claude-sonnet-4-5-20250929", - name: "claude-sonnet-4-5-20250929", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.7, output: 13.5 }, - limit: { context: 200000, output: 64000 }, - }, - "gemini-2.5-flash-lite": { - id: "gemini-2.5-flash-lite", - name: "gemini-2.5-flash-lite", - family: "gemini-flash-lite", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09, output: 0.36 }, - limit: { context: 1048576, output: 65535 }, - }, - "gpt-5.1-codex-mini": { - id: "gpt-5.1-codex-mini", - name: "gpt-5.1-codex-mini", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.225, output: 1.8 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-5.2": { - id: "gpt-5.2", - name: "gpt-5.2", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.575, output: 12.6 }, - limit: { context: 400000, output: 128000 }, - }, - "claude-haiku-4-5-20251001": { - id: "claude-haiku-4-5-20251001", - name: "claude-haiku-4-5-20251001", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.9, output: 4.5 }, - limit: { context: 20000, output: 64000 }, - }, - "o4-mini": { - id: "o4-mini", - name: "o4-mini", - family: "o", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4 }, - limit: { context: 200000, output: 100000 }, - }, - "gemini-2.5-flash-lite-preview-06-17": { - id: "gemini-2.5-flash-lite-preview-06-17", - name: "gemini-2.5-flash-lite-preview-06-17", - family: "gemini-flash-lite", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "video", "image", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09, output: 0.36 }, - limit: { context: 1048576, output: 65535 }, - }, - "gpt-5.1-codex": { - id: "gpt-5.1-codex", - name: "gpt-5.1-codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.125, output: 9 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-5.2-pro": { - id: "gpt-5.2-pro", - name: "gpt-5.2-pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 18.9, output: 151.2 }, - limit: { context: 400000, output: 128000 }, - }, - "gemini-3-pro-preview": { - id: "gemini-3-pro-preview", - name: "gemini-3-pro-preview", - family: "gemini-pro", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 1.8, output: 10.8 }, - limit: { context: 1048576, output: 65536 }, - }, - "o3-mini": { - id: "o3-mini", - name: "o3-mini", - family: "o", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4 }, - limit: { context: 131072, output: 131072 }, - }, - "grok-4-fast-non-reasoning": { - id: "grok-4-fast-non-reasoning", - name: "grok-4-fast-non-reasoning", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18, output: 0.45 }, - limit: { context: 2000000, output: 2000000 }, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - name: "gpt-5-mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.225, output: 1.8 }, - limit: { context: 400000, output: 128000 }, - }, - "claude-sonnet-4-20250514": { - id: "claude-sonnet-4-20250514", - name: "claude-sonnet-4-20250514", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.7, output: 13.5 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-opus-4-1-20250805": { - id: "claude-opus-4-1-20250805", - name: "claude-opus-4-1-20250805", - family: "claude-opus", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 13.5, output: 67.5 }, - limit: { context: 200000, output: 32000 }, - }, - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - name: "gemini-2.5-pro", - family: "gemini-pro", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video", "audio"], output: ["text"] }, - open_weights: false, - cost: { input: 1.125, output: 9 }, - limit: { context: 1048576, output: 65535 }, - }, - "gpt-5-nano": { - id: "gpt-5-nano", - name: "gpt-5-nano", - family: "gpt-nano", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.045, output: 0.36 }, - limit: { context: 400000, output: 128000 }, - }, - "zai-org/glm-4.5": { - id: "zai-org/glm-4.5", - name: "GLM-4.5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 131072, output: 98304 }, - }, - "zai-org/glm-4.7-flash": { - id: "zai-org/glm-4.7-flash", - name: "GLM-4.7-Flash", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.4 }, - limit: { context: 200000, output: 128000 }, - }, - "zai-org/glm-4.7": { - id: "zai-org/glm-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 204800, output: 131072 }, - }, - "zai-org/glm-4.5v": { - id: "zai-org/glm-4.5v", - name: "GLM 4.5V", - family: "glmv", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 1.8 }, - limit: { context: 65536, output: 16384 }, - }, - "minimaxai/minimax-m1-80k": { - id: "minimaxai/minimax-m1-80k", - name: "MiniMax M1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.2 }, - limit: { context: 1000000, output: 40000 }, - }, - "deepseek/deepseek-v3.1": { - id: "deepseek/deepseek-v3.1", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 1 }, - limit: { context: 163840, output: 32768 }, - }, - "deepseek/deepseek-r1-0528": { - id: "deepseek/deepseek-r1-0528", - name: "DeepSeek R1 0528", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 2.5 }, - limit: { context: 163840, output: 32768 }, - }, - "deepseek/deepseek-v3-0324": { - id: "deepseek/deepseek-v3-0324", - name: "DeepSeek V3 0324", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 1.14 }, - limit: { context: 163840, output: 163840 }, - }, - "moonshotai/kimi-k2-instruct": { - id: "moonshotai/kimi-k2-instruct", - name: "Kimi K2 Instruct", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.57, output: 2.3 }, - limit: { context: 131072, output: 131072 }, - }, - "moonshotai/kimi-k2-0905": { - id: "moonshotai/kimi-k2-0905", - name: "Kimi K2 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5 }, - limit: { context: 262144, output: 262144 }, - }, - "moonshotai/kimi-k2.5": { - id: "moonshotai/kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3 }, - limit: { context: 262144, output: 262144 }, - }, - "baidu/ernie-4.5-vl-424b-a47b": { - id: "baidu/ernie-4.5-vl-424b-a47b", - name: "ERNIE 4.5 VL 424B A47B", - family: "ernie", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.42, output: 1.25 }, - limit: { context: 123000, output: 16000 }, - }, - "baidu/ernie-4.5-300b-a47b-paddle": { - id: "baidu/ernie-4.5-300b-a47b-paddle", - name: "ERNIE 4.5 300B A47B", - family: "ernie", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 1.1 }, - limit: { context: 123000, output: 12000 }, - }, - "qwen/qwen3-235b-a22b-instruct-2507": { - id: "qwen/qwen3-235b-a22b-instruct-2507", - name: "Qwen3 235B A22B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.8 }, - limit: { context: 131072, output: 16384 }, - }, - "qwen/qwen3-32b-fp8": { - id: "qwen/qwen3-32b-fp8", - name: "Qwen3 32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.45 }, - limit: { context: 40960, output: 20000 }, - }, - "qwen/qwen3-next-80b-a3b-thinking": { - id: "qwen/qwen3-next-80b-a3b-thinking", - name: "Qwen3 Next 80B A3B Thinking", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 1.5 }, - limit: { context: 65536, output: 65536 }, - }, - "qwen/qwen3-coder-480b-a35b-instruct": { - id: "qwen/qwen3-coder-480b-a35b-instruct", - name: "Qwen3 Coder 480B A35B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.29, output: 1.2 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen/qwen3-30b-a3b-fp8": { - id: "qwen/qwen3-30b-a3b-fp8", - name: "Qwen3 30B A3B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.45 }, - limit: { context: 40960, output: 20000 }, - }, - "qwen/qwen3-coder-next": { - id: "qwen/qwen3-coder-next", - name: "qwen/qwen3-coder-next", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02", - last_updated: "2026-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 1.5 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen/qwen3-235b-a22b-thinking-2507": { - id: "qwen/qwen3-235b-a22b-thinking-2507", - name: "Qwen3 235B A22b Thinking 2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 3 }, - limit: { context: 131072, output: 131072 }, - }, - "qwen/qwen3-next-80b-a3b-instruct": { - id: "qwen/qwen3-next-80b-a3b-instruct", - name: "Qwen3 Next 80B A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 1.5 }, - limit: { context: 65536, output: 65536 }, - }, - "qwen/qwen3-235b-a22b-fp8": { - id: "qwen/qwen3-235b-a22b-fp8", - name: "Qwen3 235B A22B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 40960, output: 20000 }, - }, - "minimax/minimax-m2.1": { - id: "minimax/minimax-m2.1", - name: "Minimax M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 204800, output: 131072 }, - }, - "xiaomimimo/mimo-v2-flash": { - id: "xiaomimimo/mimo-v2-flash", - name: "XiaomiMiMo/MiMo-V2-Flash", - family: "mimo", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 131072 }, - }, - }, - }, - meganova: { - id: "meganova", - env: ["MEGANOVA_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.meganova.ai/v1", - name: "Meganova", - doc: "https://docs.meganova.ai", - models: { - "zai-org/GLM-4.6": { - id: "zai-org/GLM-4.6", - name: "GLM-4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.45, output: 1.9 }, - limit: { context: 202752, output: 131072 }, - }, - "zai-org/GLM-4.7": { - id: "zai-org/GLM-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 202752, output: 131072 }, - }, - "zai-org/GLM-5": { - id: "zai-org/GLM-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.8, output: 2.56 }, - limit: { context: 202752, output: 131072 }, - }, - "XiaomiMiMo/MiMo-V2-Flash": { - id: "XiaomiMiMo/MiMo-V2-Flash", - name: "MiMo V2 Flash", - family: "mimo", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-12-01", - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 262144, output: 32000 }, - }, - "MiniMaxAI/MiniMax-M2.5": { - id: "MiniMaxAI/MiniMax-M2.5", - name: "MiniMax M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 204800, output: 131072 }, - }, - "MiniMaxAI/MiniMax-M2.1": { - id: "MiniMaxAI/MiniMax-M2.1", - name: "MiniMax M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 1.2 }, - limit: { context: 196608, output: 131072 }, - }, - "deepseek-ai/DeepSeek-V3.2-Exp": { - id: "deepseek-ai/DeepSeek-V3.2-Exp", - name: "DeepSeek V3.2 Exp", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-10-10", - last_updated: "2025-10-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 0.4 }, - limit: { context: 164000, output: 164000 }, - }, - "deepseek-ai/DeepSeek-R1-0528": { - id: "deepseek-ai/DeepSeek-R1-0528", - name: "DeepSeek R1 0528", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: false, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-07", - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 2.15 }, - limit: { context: 163840, output: 64000 }, - }, - "deepseek-ai/DeepSeek-V3.1": { - id: "deepseek-ai/DeepSeek-V3.1", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-08-25", - last_updated: "2025-08-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 1 }, - limit: { context: 164000, output: 164000 }, - }, - "deepseek-ai/DeepSeek-V3.2": { - id: "deepseek-ai/DeepSeek-V3.2", - name: "DeepSeek V3.2", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-03", - last_updated: "2025-12-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.26, output: 0.38 }, - limit: { context: 164000, output: 164000 }, - }, - "deepseek-ai/DeepSeek-V3-0324": { - id: "deepseek-ai/DeepSeek-V3-0324", - name: "DeepSeek V3 0324", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-03-24", - last_updated: "2025-03-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 0.88 }, - limit: { context: 163840, output: 163840 }, - }, - "moonshotai/Kimi-K2.5": { - id: "moonshotai/Kimi-K2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2026-01", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.45, output: 2.8 }, - limit: { context: 262144, output: 262144 }, - }, - "moonshotai/Kimi-K2-Thinking": { - id: "moonshotai/Kimi-K2-Thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-08", - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.6 }, - limit: { context: 262144, output: 262144 }, - }, - "meta-llama/Llama-3.3-70B-Instruct": { - id: "meta-llama/Llama-3.3-70B-Instruct", - name: "Llama 3.3 70B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 131072, output: 16384 }, - }, - "Qwen/Qwen3.5-Plus": { - id: "Qwen/Qwen3.5-Plus", - name: "Qwen3.5 Plus", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02", - last_updated: "2026-02", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2.4, reasoning: 2.4 }, - limit: { context: 1000000, output: 65536 }, - }, - "Qwen/Qwen3-235B-A22B-Instruct-2507": { - id: "Qwen/Qwen3-235B-A22B-Instruct-2507", - name: "Qwen3 235B A22B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.6 }, - limit: { context: 262000, output: 262000 }, - }, - "Qwen/Qwen2.5-VL-32B-Instruct": { - id: "Qwen/Qwen2.5-VL-32B-Instruct", - name: "Qwen2.5 VL 32B Instruct", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-03-24", - last_updated: "2025-03-24", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 16384, output: 16384 }, - }, - "mistralai/Mistral-Nemo-Instruct-2407": { - id: "mistralai/Mistral-Nemo-Instruct-2407", - name: "Mistral Nemo Instruct 2407", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.04 }, - limit: { context: 131072, output: 65536 }, - }, - "mistralai/Mistral-Small-3.2-24B-Instruct-2506": { - id: "mistralai/Mistral-Small-3.2-24B-Instruct-2506", - name: "Mistral Small 3.2 24B Instruct", - family: "mistral-small", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-06-20", - last_updated: "2025-06-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 8192 }, - }, - }, - }, - perplexity: { - id: "perplexity", - env: ["PERPLEXITY_API_KEY"], - npm: "@ai-sdk/perplexity", - name: "Perplexity", - doc: "https://docs.perplexity.ai", - models: { - "sonar-reasoning-pro": { - id: "sonar-reasoning-pro", - name: "Sonar Reasoning Pro", - family: "sonar-reasoning", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2025-09-01", - release_date: "2024-01-01", - last_updated: "2025-09-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8 }, - limit: { context: 128000, output: 4096 }, - }, - sonar: { - id: "sonar", - name: "Sonar", - family: "sonar", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-09-01", - release_date: "2024-01-01", - last_updated: "2025-09-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 1 }, - limit: { context: 128000, output: 4096 }, - }, - "sonar-deep-research": { - id: "sonar-deep-research", - name: "Perplexity Sonar Deep Research", - attachment: false, - reasoning: true, - tool_call: false, - temperature: false, - knowledge: "2025-01", - release_date: "2025-02-01", - last_updated: "2025-09-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, reasoning: 3 }, - limit: { context: 128000, output: 32768 }, - }, - "sonar-pro": { - id: "sonar-pro", - name: "Sonar Pro", - family: "sonar-pro", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2025-09-01", - release_date: "2024-01-01", - last_updated: "2025-09-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, output: 8192 }, - }, - }, - }, - huggingface: { - id: "huggingface", - env: ["HF_TOKEN"], - npm: "@ai-sdk/openai-compatible", - api: "https://router.huggingface.co/v1", - name: "Hugging Face", - doc: "https://huggingface.co/docs/inference-providers", - models: { - "zai-org/GLM-4.7-Flash": { - id: "zai-org/GLM-4.7-Flash", - name: "GLM-4.7-Flash", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-08-08", - last_updated: "2025-08-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 200000, output: 128000 }, - }, - "zai-org/GLM-4.7": { - id: "zai-org/GLM-4.7", - name: "GLM-4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.11 }, - limit: { context: 204800, output: 131072 }, - }, - "zai-org/GLM-5": { - id: "zai-org/GLM-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2, cache_read: 0.2 }, - limit: { context: 202752, output: 131072 }, - }, - "XiaomiMiMo/MiMo-V2-Flash": { - id: "XiaomiMiMo/MiMo-V2-Flash", - name: "MiMo-V2-Flash", - family: "mimo", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-12-16", - last_updated: "2025-12-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 262144, output: 4096 }, - }, - "MiniMaxAI/MiniMax-M2.5": { - id: "MiniMaxAI/MiniMax-M2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.03 }, - limit: { context: 204800, output: 131072 }, - }, - "MiniMaxAI/MiniMax-M2.1": { - id: "MiniMaxAI/MiniMax-M2.1", - name: "MiniMax-M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-10", - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 204800, output: 131072 }, - }, - "deepseek-ai/DeepSeek-R1-0528": { - id: "deepseek-ai/DeepSeek-R1-0528", - name: "DeepSeek-R1-0528", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 3, output: 5 }, - limit: { context: 163840, output: 163840 }, - }, - "deepseek-ai/DeepSeek-V3.2": { - id: "deepseek-ai/DeepSeek-V3.2", - name: "DeepSeek-V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 0.4 }, - limit: { context: 163840, output: 65536 }, - }, - "moonshotai/Kimi-K2-Instruct": { - id: "moonshotai/Kimi-K2-Instruct", - name: "Kimi-K2-Instruct", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-07-14", - last_updated: "2025-07-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3 }, - limit: { context: 131072, output: 16384 }, - }, - "moonshotai/Kimi-K2-Instruct-0905": { - id: "moonshotai/Kimi-K2-Instruct-0905", - name: "Kimi-K2-Instruct-0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-10", - release_date: "2025-09-04", - last_updated: "2025-09-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3 }, - limit: { context: 262144, output: 16384 }, - }, - "moonshotai/Kimi-K2.5": { - id: "moonshotai/Kimi-K2.5", - name: "Kimi-K2.5", - family: "kimi", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01-01", - last_updated: "2026-01-01", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3, cache_read: 0.1 }, - limit: { context: 262144, output: 262144 }, - }, - "moonshotai/Kimi-K2-Thinking": { - id: "moonshotai/Kimi-K2-Thinking", - name: "Kimi-K2-Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-08", - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5, cache_read: 0.15 }, - limit: { context: 262144, output: 262144 }, - }, - "Qwen/Qwen3-Next-80B-A3B-Instruct": { - id: "Qwen/Qwen3-Next-80B-A3B-Instruct", - name: "Qwen3-Next-80B-A3B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-11", - last_updated: "2025-09-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 1 }, - limit: { context: 262144, output: 66536 }, - }, - "Qwen/Qwen3.5-397B-A17B": { - id: "Qwen/Qwen3.5-397B-A17B", - name: "Qwen3.5-397B-A17B", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-01", - last_updated: "2026-02-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3.6 }, - limit: { context: 262144, output: 32768 }, - }, - "Qwen/Qwen3-235B-A22B-Thinking-2507": { - id: "Qwen/Qwen3-235B-A22B-Thinking-2507", - name: "Qwen3-235B-A22B-Thinking-2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-25", - last_updated: "2025-07-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 3 }, - limit: { context: 262144, output: 131072 }, - }, - "Qwen/Qwen3-Coder-Next": { - id: "Qwen/Qwen3-Coder-Next", - name: "Qwen3-Coder-Next", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-03", - last_updated: "2026-02-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 1.5 }, - limit: { context: 262144, output: 65536 }, - }, - "Qwen/Qwen3-Coder-480B-A35B-Instruct": { - id: "Qwen/Qwen3-Coder-480B-A35B-Instruct", - name: "Qwen3-Coder-480B-A35B-Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 2 }, - limit: { context: 262144, output: 66536 }, - }, - "Qwen/Qwen3-Embedding-4B": { - id: "Qwen/Qwen3-Embedding-4B", - name: "Qwen 3 Embedding 4B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2024-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0 }, - limit: { context: 32000, output: 2048 }, - }, - "Qwen/Qwen3-Embedding-8B": { - id: "Qwen/Qwen3-Embedding-8B", - name: "Qwen 3 Embedding 8B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2024-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0 }, - limit: { context: 32000, output: 4096 }, - }, - "Qwen/Qwen3-Next-80B-A3B-Thinking": { - id: "Qwen/Qwen3-Next-80B-A3B-Thinking", - name: "Qwen3-Next-80B-A3B-Thinking", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-11", - last_updated: "2025-09-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 2 }, - limit: { context: 262144, output: 131072 }, - }, - }, - }, - anthropic: { - id: "anthropic", - env: ["ANTHROPIC_API_KEY"], - npm: "@ai-sdk/anthropic", - name: "Anthropic", - doc: "https://docs.anthropic.com/en/docs/about-claude/models", - models: { - "claude-opus-4-5-20251101": { - id: "claude-opus-4-5-20251101", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-01", - last_updated: "2025-11-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-3-5-haiku-latest": { - id: "claude-3-5-haiku-latest", - name: "Claude Haiku 3.5 (latest)", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07-31", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 }, - limit: { context: 200000, output: 8192 }, - }, - "claude-opus-4-1": { - id: "claude-opus-4-1", - name: "Claude Opus 4.1 (latest)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "claude-3-5-sonnet-20241022": { - id: "claude-3-5-sonnet-20241022", - name: "Claude Sonnet 3.5 v2", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04-30", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 8192 }, - }, - "claude-3-sonnet-20240229": { - id: "claude-3-sonnet-20240229", - name: "Claude Sonnet 3", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-08-31", - release_date: "2024-03-04", - last_updated: "2024-03-04", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 0.3 }, - limit: { context: 200000, output: 4096 }, - }, - "claude-opus-4-6": { - id: "claude-opus-4-6", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02-05", - last_updated: "2026-03-13", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 1000000, output: 128000 }, - }, - "claude-sonnet-4-6": { - id: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2026-02-17", - last_updated: "2026-03-13", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 1000000, output: 64000 }, - }, - "claude-sonnet-4-0": { - id: "claude-sonnet-4-0", - name: "Claude Sonnet 4 (latest)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-opus-4-20250514": { - id: "claude-opus-4-20250514", - name: "Claude Opus 4", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "claude-sonnet-4-5-20250929": { - id: "claude-sonnet-4-5-20250929", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-opus-4-0": { - id: "claude-opus-4-0", - name: "Claude Opus 4 (latest)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "claude-3-5-haiku-20241022": { - id: "claude-3-5-haiku-20241022", - name: "Claude Haiku 3.5", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07-31", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 }, - limit: { context: 200000, output: 8192 }, - }, - "claude-3-5-sonnet-20240620": { - id: "claude-3-5-sonnet-20240620", - name: "Claude Sonnet 3.5", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04-30", - release_date: "2024-06-20", - last_updated: "2024-06-20", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 8192 }, - }, - "claude-3-7-sonnet-latest": { - id: "claude-3-7-sonnet-latest", - name: "Claude Sonnet 3.7 (latest)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10-31", - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-3-7-sonnet-20250219": { - id: "claude-3-7-sonnet-20250219", - name: "Claude Sonnet 3.7", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10-31", - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-3-haiku-20240307": { - id: "claude-3-haiku-20240307", - name: "Claude Haiku 3", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-08-31", - release_date: "2024-03-13", - last_updated: "2024-03-13", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1.25, cache_read: 0.03, cache_write: 0.3 }, - limit: { context: 200000, output: 4096 }, - }, - "claude-haiku-4-5-20251001": { - id: "claude-haiku-4-5-20251001", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-haiku-4-5": { - id: "claude-haiku-4-5", - name: "Claude Haiku 4.5 (latest)", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-opus-4-5": { - id: "claude-opus-4-5", - name: "Claude Opus 4.5 (latest)", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-11-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-3-opus-20240229": { - id: "claude-3-opus-20240229", - name: "Claude Opus 3", - family: "claude-opus", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-08-31", - release_date: "2024-02-29", - last_updated: "2024-02-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 4096 }, - }, - "claude-sonnet-4-5": { - id: "claude-sonnet-4-5", - name: "Claude Sonnet 4.5 (latest)", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-sonnet-4-20250514": { - id: "claude-sonnet-4-20250514", - name: "Claude Sonnet 4", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-opus-4-1-20250805": { - id: "claude-opus-4-1-20250805", - name: "Claude Opus 4.1", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - }, - }, - "google-vertex-anthropic": { - id: "google-vertex-anthropic", - env: ["GOOGLE_VERTEX_PROJECT", "GOOGLE_VERTEX_LOCATION", "GOOGLE_APPLICATION_CREDENTIALS"], - npm: "@ai-sdk/google-vertex/anthropic", - name: "Vertex (Anthropic)", - doc: "https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude", - models: { - "claude-sonnet-4-5@20250929": { - id: "claude-sonnet-4-5@20250929", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-opus-4-1@20250805": { - id: "claude-opus-4-1@20250805", - name: "Claude Opus 4.1", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "claude-3-7-sonnet@20250219": { - id: "claude-3-7-sonnet@20250219", - name: "Claude Sonnet 3.7", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-10-31", - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-opus-4@20250514": { - id: "claude-opus-4@20250514", - name: "Claude Opus 4", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "claude-opus-4-5@20251101": { - id: "claude-opus-4-5@20251101", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-11-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-3-5-haiku@20241022": { - id: "claude-3-5-haiku@20241022", - name: "Claude Haiku 3.5", - family: "claude-haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07-31", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 }, - limit: { context: 200000, output: 8192 }, - }, - "claude-sonnet-4@20250514": { - id: "claude-sonnet-4@20250514", - name: "Claude Sonnet 4", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-3-5-sonnet@20241022": { - id: "claude-3-5-sonnet@20241022", - name: "Claude Sonnet 3.5 v2", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04-30", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 8192 }, - }, - "claude-opus-4-6@default": { - id: "claude-opus-4-6@default", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 5, - output: 25, - cache_read: 0.5, - cache_write: 6.25, - context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 }, - }, - limit: { context: 1000000, output: 128000 }, - }, - "claude-haiku-4-5@20251001": { - id: "claude-haiku-4-5@20251001", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-02-28", - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-sonnet-4-6@default": { - id: "claude-sonnet-4-6@default", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-08", - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 3, - output: 15, - cache_read: 0.3, - cache_write: 3.75, - context_over_200k: { input: 6, output: 22.5, cache_read: 0.6, cache_write: 7.5 }, - }, - limit: { context: 200000, output: 64000 }, - }, - }, - }, - friendli: { - id: "friendli", - env: ["FRIENDLI_TOKEN"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.friendli.ai/serverless/v1", - name: "Friendli", - doc: "https://friendli.ai/docs/guides/serverless_endpoints/introduction", - models: { - "zai-org/GLM-4.7": { - id: "zai-org/GLM-4.7", - name: "GLM 4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-22", - last_updated: "2026-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - limit: { context: 202752, output: 202752 }, - }, - "zai-org/GLM-5": { - id: "zai-org/GLM-5", - name: "GLM 5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3.2 }, - limit: { context: 202752, output: 202752 }, - }, - "MiniMaxAI/MiniMax-M2.5": { - id: "MiniMaxAI/MiniMax-M2.5", - name: "MiniMax M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 196608, output: 196608 }, - }, - "MiniMaxAI/MiniMax-M2.1": { - id: "MiniMaxAI/MiniMax-M2.1", - name: "MiniMax M2.1", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-01-13", - last_updated: "2026-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 196608, output: 196608 }, - }, - "meta-llama/Llama-3.1-8B-Instruct": { - id: "meta-llama/Llama-3.1-8B-Instruct", - name: "Llama 3.1 8B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-08-01", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 131072, output: 8000 }, - }, - "meta-llama/Llama-3.3-70B-Instruct": { - id: "meta-llama/Llama-3.3-70B-Instruct", - name: "Llama 3.3 70B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-08-01", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 0.6 }, - limit: { context: 131072, output: 131072 }, - }, - "Qwen/Qwen3-235B-A22B-Instruct-2507": { - id: "Qwen/Qwen3-235B-A22B-Instruct-2507", - name: "Qwen3 235B A22B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-07-29", - last_updated: "2026-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 262144, output: 262144 }, - }, - }, - }, - kilo: { - id: "kilo", - env: ["KILO_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.kilo.ai/api/gateway", - name: "Kilo Gateway", - doc: "https://kilo.ai", - models: { - "giga-potato-thinking": { - id: "giga-potato-thinking", - name: "Giga Potato Thinking (free)", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-27", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 32000 }, - }, - "corethink:free": { - id: "corethink:free", - name: "CoreThink (free)", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-08-27", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 78000, output: 8192 }, - }, - "morph-warp-grep-v2": { - id: "morph-warp-grep-v2", - name: "Morph: WarpGrep V2", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-08-27", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 32000 }, - }, - "giga-potato": { - id: "giga-potato", - name: "Giga Potato (free)", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-08-27", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 32000 }, - }, - "prime-intellect/intellect-3": { - id: "prime-intellect/intellect-3", - name: "Prime Intellect: INTELLECT-3", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-11-26", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 1.1 }, - limit: { context: 131072, output: 131072 }, - }, - "allenai/olmo-2-0325-32b-instruct": { - id: "allenai/olmo-2-0325-32b-instruct", - name: "AllenAI: Olmo 2 32B Instruct", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2025-03-15", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.2 }, - limit: { context: 128000, output: 32768 }, - }, - "allenai/olmo-3-7b-instruct": { - id: "allenai/olmo-3-7b-instruct", - name: "AllenAI: Olmo 3 7B Instruct", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-11-22", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.2 }, - limit: { context: 65536, output: 65536 }, - }, - "allenai/olmo-3-32b-think": { - id: "allenai/olmo-3-32b-think", - name: "AllenAI: Olmo 3 32B Think", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-11-22", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.5 }, - limit: { context: 65536, output: 65536 }, - }, - "allenai/molmo-2-8b": { - id: "allenai/molmo-2-8b", - name: "AllenAI: Molmo2 8B", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2026-01-09", - last_updated: "2026-01-31", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 36864, output: 36864 }, - }, - "allenai/olmo-3.1-32b-instruct": { - id: "allenai/olmo-3.1-32b-instruct", - name: "AllenAI: Olmo 3.1 32B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2026-01-07", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 65536, output: 32768 }, - }, - "allenai/olmo-3-7b-think": { - id: "allenai/olmo-3-7b-think", - name: "AllenAI: Olmo 3 7B Think", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-11-22", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.12, output: 0.2 }, - limit: { context: 65536, output: 65536 }, - }, - "allenai/olmo-3.1-32b-think": { - id: "allenai/olmo-3.1-32b-think", - name: "AllenAI: Olmo 3.1 32B Think", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-12-17", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.5 }, - limit: { context: 65536, output: 65536 }, - }, - "nex-agi/deepseek-v3.1-nex-n1": { - id: "nex-agi/deepseek-v3.1-nex-n1", - name: "Nex AGI: DeepSeek V3.1 Nex N1", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 1 }, - limit: { context: 131072, output: 163840 }, - }, - "nvidia/llama-3.1-nemotron-70b-instruct": { - id: "nvidia/llama-3.1-nemotron-70b-instruct", - name: "NVIDIA: Llama 3.1 Nemotron 70B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-10-12", - last_updated: "2024-10-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.2, output: 1.2 }, - limit: { context: 131072, output: 16384 }, - }, - "nvidia/nemotron-3-super-120b-a12b:free": { - id: "nvidia/nemotron-3-super-120b-a12b:free", - name: "NVIDIA: Nemotron 3 Super (free)", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-12", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 262144 }, - }, - "nvidia/llama-3.3-nemotron-super-49b-v1.5": { - id: "nvidia/llama-3.3-nemotron-super-49b-v1.5", - name: "NVIDIA: Llama 3.3 Nemotron Super 49B V1.5", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-03-16", - last_updated: "2025-03-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 131072, output: 26215 }, - }, - "nvidia/nemotron-nano-12b-v2-vl": { - id: "nvidia/nemotron-nano-12b-v2-vl", - name: "NVIDIA: Nemotron Nano 12B 2 VL", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-10-28", - last_updated: "2026-01-31", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 131072, output: 26215 }, - }, - "nvidia/nemotron-nano-9b-v2": { - id: "nvidia/nemotron-nano-9b-v2", - name: "NVIDIA: Nemotron Nano 9B V2", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-18", - last_updated: "2025-08-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.16 }, - limit: { context: 131072, output: 26215 }, - }, - "nvidia/nemotron-3-nano-30b-a3b": { - id: "nvidia/nemotron-3-nano-30b-a3b", - name: "NVIDIA: Nemotron 3 Nano 30B A3B", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2024-12", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.2 }, - limit: { context: 262144, output: 52429 }, - }, - "ibm-granite/granite-4.0-h-micro": { - id: "ibm-granite/granite-4.0-h-micro", - name: "IBM: Granite 4.0 Micro", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-10-20", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.017, output: 0.11 }, - limit: { context: 131000, output: 32768 }, - }, - "arcee-ai/coder-large": { - id: "arcee-ai/coder-large", - name: "Arcee AI: Coder Large", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-05-06", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 0.8 }, - limit: { context: 32768, output: 32768 }, - }, - "arcee-ai/virtuoso-large": { - id: "arcee-ai/virtuoso-large", - name: "Arcee AI: Virtuoso Large", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-05-06", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.75, output: 1.2 }, - limit: { context: 131072, output: 64000 }, - }, - "arcee-ai/trinity-mini": { - id: "arcee-ai/trinity-mini", - name: "Arcee AI: Trinity Mini", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12", - last_updated: "2026-01-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.045, output: 0.15 }, - limit: { context: 131072, output: 131072 }, - }, - "arcee-ai/maestro-reasoning": { - id: "arcee-ai/maestro-reasoning", - name: "Arcee AI: Maestro Reasoning", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-05-06", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.9, output: 3.3 }, - limit: { context: 131072, output: 32000 }, - }, - "arcee-ai/trinity-large-preview:free": { - id: "arcee-ai/trinity-large-preview:free", - name: "Arcee AI: Trinity Large Preview (free)", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2026-01-28", - last_updated: "2026-01-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 131000, output: 26200 }, - }, - "arcee-ai/spotlight": { - id: "arcee-ai/spotlight", - name: "Arcee AI: Spotlight", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-05-06", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.18, output: 0.18 }, - limit: { context: 131072, output: 65537 }, - }, - "xiaomi/mimo-v2-flash": { - id: "xiaomi/mimo-v2-flash", - name: "Xiaomi: MiMo-V2-Flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-14", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.29, cache_read: 0.045 }, - limit: { context: 262144, output: 65536 }, - }, - "microsoft/phi-4": { - id: "microsoft/phi-4", - name: "Microsoft: Phi 4", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.14 }, - limit: { context: 16384, output: 16384 }, - }, - "microsoft/wizardlm-2-8x22b": { - id: "microsoft/wizardlm-2-8x22b", - name: "WizardLM-2 8x22B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-04-24", - last_updated: "2024-04-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.62, output: 0.62 }, - limit: { context: 65535, output: 8000 }, - }, - "alfredpros/codellama-7b-instruct-solidity": { - id: "alfredpros/codellama-7b-instruct-solidity", - name: "AlfredPros: CodeLLaMa 7B Instruct Solidity", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-14", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.8, output: 1.2 }, - limit: { context: 4096, output: 4096 }, - }, - "liquid/lfm-2.2-6b": { - id: "liquid/lfm-2.2-6b", - name: "LiquidAI: LFM2-2.6B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-10-20", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0.02 }, - limit: { context: 32768, output: 32768 }, - }, - "liquid/lfm-2-24b-a2b": { - id: "liquid/lfm-2-24b-a2b", - name: "LiquidAI: LFM2-24B-A2B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2026-02-26", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.12 }, - limit: { context: 32768, output: 32768 }, - }, - "liquid/lfm2-8b-a1b": { - id: "liquid/lfm2-8b-a1b", - name: "LiquidAI: LFM2-8B-A1B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-10-20", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0.02 }, - limit: { context: 32768, output: 32768 }, - }, - "upstage/solar-pro-3": { - id: "upstage/solar-pro-3", - name: "Upstage: Solar Pro 3", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-01-27", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 32768 }, - }, - "switchpoint/router": { - id: "switchpoint/router", - name: "Switchpoint Router", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-07-12", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.85, output: 3.4 }, - limit: { context: 131072, output: 32768 }, - }, - "inception/mercury-2": { - id: "inception/mercury-2", - name: "Inception: Mercury 2", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-24", - last_updated: "2026-02-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.75, cache_read: 0.025 }, - limit: { context: 128000, output: 50000 }, - }, - "inception/mercury": { - id: "inception/mercury", - name: "Inception: Mercury", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-06-26", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.75 }, - limit: { context: 128000, output: 32000 }, - }, - "inception/mercury-coder": { - id: "inception/mercury-coder", - name: "Inception: Mercury Coder", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-02-26", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.75 }, - limit: { context: 128000, output: 32000 }, - }, - "kilo-auto/balanced": { - id: "kilo-auto/balanced", - name: "Kilo Auto Balanced", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-15", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 3 }, - limit: { context: 204800, output: 131072 }, - }, - "kilo-auto/free": { - id: "kilo-auto/free", - name: "Kilo Auto Free", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-15", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "kilo-auto/small": { - id: "kilo-auto/small", - name: "Kilo Auto Small", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-15", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4 }, - limit: { context: 400000, output: 128000 }, - }, - "kilo-auto/frontier": { - id: "kilo-auto/frontier", - name: "Kilo Auto Frontier", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-15", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25 }, - limit: { context: 1000000, output: 128000 }, - }, - "amazon/nova-micro-v1": { - id: "amazon/nova-micro-v1", - name: "Amazon: Nova Micro 1.0", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-06", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.035, output: 0.14 }, - limit: { context: 128000, output: 5120 }, - }, - "amazon/nova-lite-v1": { - id: "amazon/nova-lite-v1", - name: "Amazon: Nova Lite 1.0", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-06", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.06, output: 0.24 }, - limit: { context: 300000, output: 5120 }, - }, - "amazon/nova-premier-v1": { - id: "amazon/nova-premier-v1", - name: "Amazon: Nova Premier 1.0", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-11-01", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 12.5 }, - limit: { context: 1000000, output: 32000 }, - }, - "amazon/nova-2-lite-v1": { - id: "amazon/nova-2-lite-v1", - name: "Amazon: Nova 2 Lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5 }, - limit: { context: 1000000, output: 65535 }, - }, - "amazon/nova-pro-v1": { - id: "amazon/nova-pro-v1", - name: "Amazon: Nova Pro 1.0", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 3.2 }, - limit: { context: 300000, output: 5120 }, - }, - "anthracite-org/magnum-v4-72b": { - id: "anthracite-org/magnum-v4-72b", - name: "Magnum v4 72B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-10-22", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 3, output: 5 }, - limit: { context: 16384, output: 2048 }, - }, - "essentialai/rnj-1-instruct": { - id: "essentialai/rnj-1-instruct", - name: "EssentialAI: Rnj 1 Instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-12-05", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 32768, output: 6554 }, - }, - "gryphe/mythomax-l2-13b": { - id: "gryphe/mythomax-l2-13b", - name: "MythoMax 13B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-04-25", - last_updated: "2024-04-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.06 }, - limit: { context: 4096, output: 4096 }, - }, - "alibaba/tongyi-deepresearch-30b-a3b": { - id: "alibaba/tongyi-deepresearch-30b-a3b", - name: "Tongyi DeepResearch 30B A3B", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-09-18", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.09, output: 0.45 }, - limit: { context: 131072, output: 131072 }, - }, - "aion-labs/aion-1.0-mini": { - id: "aion-labs/aion-1.0-mini", - name: "AionLabs: Aion-1.0-Mini", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-02-05", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7, output: 1.4 }, - limit: { context: 131072, output: 32768 }, - }, - "aion-labs/aion-2.0": { - id: "aion-labs/aion-2.0", - name: "AionLabs: Aion-2.0", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2026-02-24", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 1.6 }, - limit: { context: 131072, output: 32768 }, - }, - "aion-labs/aion-rp-llama-3.1-8b": { - id: "aion-labs/aion-rp-llama-3.1-8b", - name: "AionLabs: Aion-RP 1.0 (8B)", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-02-05", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 1.6 }, - limit: { context: 32768, output: 32768 }, - }, - "aion-labs/aion-1.0": { - id: "aion-labs/aion-1.0", - name: "AionLabs: Aion-1.0", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-02-05", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 4, output: 8 }, - limit: { context: 131072, output: 32768 }, - }, - "stepfun/step-3.5-flash:free": { - id: "stepfun/step-3.5-flash:free", - name: "StepFun: Step 3.5 Flash (free)", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-01-29", - last_updated: "2026-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 256000 }, - }, - "stepfun/step-3.5-flash": { - id: "stepfun/step-3.5-flash", - name: "StepFun: Step 3.5 Flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-01-29", - last_updated: "2026-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3, cache_read: 0.02 }, - limit: { context: 256000, output: 256000 }, - }, - "relace/relace-search": { - id: "relace/relace-search", - name: "Relace: Relace Search", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-12-09", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 3 }, - limit: { context: 256000, output: 128000 }, - }, - "relace/relace-apply-3": { - id: "relace/relace-apply-3", - name: "Relace: Relace Apply 3", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2025-09-26", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.85, output: 1.25 }, - limit: { context: 256000, output: 128000 }, - }, - "thedrummer/rocinante-12b": { - id: "thedrummer/rocinante-12b", - name: "TheDrummer: Rocinante 12B", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-09-30", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.17, output: 0.43 }, - limit: { context: 32768, output: 32768 }, - }, - "thedrummer/cydonia-24b-v4.1": { - id: "thedrummer/cydonia-24b-v4.1", - name: "TheDrummer: Cydonia 24B V4.1", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-09-27", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.5 }, - limit: { context: 131072, output: 131072 }, - }, - "thedrummer/unslopnemo-12b": { - id: "thedrummer/unslopnemo-12b", - name: "TheDrummer: UnslopNemo 12B", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-11-09", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 0.4 }, - limit: { context: 32768, output: 32768 }, - }, - "thedrummer/skyfall-36b-v2": { - id: "thedrummer/skyfall-36b-v2", - name: "TheDrummer: Skyfall 36B V2", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-03-11", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 0.8 }, - limit: { context: 32768, output: 32768 }, - }, - "mancer/weaver": { - id: "mancer/weaver", - name: "Mancer: Weaver (alpha)", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2023-08-02", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.75, output: 1 }, - limit: { context: 8000, output: 2000 }, - }, - "tencent/hunyuan-a13b-instruct": { - id: "tencent/hunyuan-a13b-instruct", - name: "Tencent: Hunyuan A13B Instruct", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-06-30", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0.57 }, - limit: { context: 131072, output: 131072 }, - }, - "kwaipilot/kat-coder-pro": { - id: "kwaipilot/kat-coder-pro", - name: "Kwaipilot: KAT-Coder-Pro V1", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-09-30", - last_updated: "2025-10-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.207, output: 0.828, cache_read: 0.0414 }, - limit: { context: 256000, output: 128000 }, - }, - "deepseek/deepseek-r1-0528": { - id: "deepseek/deepseek-r1-0528", - name: "DeepSeek: R1 0528", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-05-28", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.45, output: 2.15, cache_read: 0.2 }, - limit: { context: 163840, output: 65536 }, - }, - "deepseek/deepseek-r1": { - id: "deepseek/deepseek-r1", - name: "DeepSeek: R1", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 2.5 }, - limit: { context: 64000, output: 16000 }, - }, - "deepseek/deepseek-v3.2-speciale": { - id: "deepseek/deepseek-v3.2-speciale", - name: "DeepSeek: DeepSeek V3.2 Speciale", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-12-01", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 1.2, cache_read: 0.135 }, - limit: { context: 163840, output: 163840 }, - }, - "deepseek/deepseek-chat-v3.1": { - id: "deepseek/deepseek-chat-v3.1", - name: "DeepSeek: DeepSeek V3.1", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.75 }, - limit: { context: 32768, output: 7168 }, - }, - "deepseek/deepseek-chat-v3-0324": { - id: "deepseek/deepseek-chat-v3-0324", - name: "DeepSeek: DeepSeek V3 0324", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-03-24", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.77, cache_read: 0.095 }, - limit: { context: 163840, output: 65536 }, - }, - "deepseek/deepseek-r1-distill-llama-70b": { - id: "deepseek/deepseek-r1-distill-llama-70b", - name: "DeepSeek: R1 Distill Llama 70B", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-01-23", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.7, output: 0.8, cache_read: 0.015 }, - limit: { context: 131072, output: 16384 }, - }, - "deepseek/deepseek-v3.1-terminus": { - id: "deepseek/deepseek-v3.1-terminus", - name: "DeepSeek: DeepSeek V3.1 Terminus", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-09-22", - last_updated: "2025-09-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.21, output: 0.79, cache_read: 0.13 }, - limit: { context: 163840, output: 32768 }, - }, - "deepseek/deepseek-v3.2": { - id: "deepseek/deepseek-v3.2", - name: "DeepSeek: DeepSeek V3.2", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-01", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.26, output: 0.38, cache_read: 0.125 }, - limit: { context: 163840, output: 65536 }, - }, - "deepseek/deepseek-chat": { - id: "deepseek/deepseek-chat", - name: "DeepSeek: DeepSeek V3", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.32, output: 0.89, cache_read: 0.15 }, - limit: { context: 163840, output: 163840 }, - }, - "deepseek/deepseek-r1-distill-qwen-32b": { - id: "deepseek/deepseek-r1-distill-qwen-32b", - name: "DeepSeek: R1 Distill Qwen 32B", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.29, output: 0.29 }, - limit: { context: 32768, output: 32768 }, - }, - "deepseek/deepseek-v3.2-exp": { - id: "deepseek/deepseek-v3.2-exp", - name: "DeepSeek: DeepSeek V3.2 Exp", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-09-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 0.41 }, - limit: { context: 163840, output: 65536 }, - }, - "alpindale/goliath-120b": { - id: "alpindale/goliath-120b", - name: "Goliath 120B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2023-11-10", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 3.75, output: 7.5 }, - limit: { context: 6144, output: 1024 }, - }, - "openrouter/hunter-alpha": { - id: "openrouter/hunter-alpha", - name: "Hunter Alpha", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-12", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 1048576, output: 32000 }, - }, - "openrouter/auto": { - id: "openrouter/auto", - name: "Auto Router", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-15", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text", "video"], output: ["image", "text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 2000000, output: 32768 }, - }, - "openrouter/free": { - id: "openrouter/free", - name: "Free Models Router", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-01", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 200000, output: 32768 }, - }, - "openrouter/healer-alpha": { - id: "openrouter/healer-alpha", - name: "Healer Alpha", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-12", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 262144, output: 32000 }, - }, - "openrouter/bodybuilder": { - id: "openrouter/bodybuilder", - name: "Body Builder (beta)", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2026-03-15", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - status: "beta", - }, - "moonshotai/kimi-k2": { - id: "moonshotai/kimi-k2", - name: "MoonshotAI: Kimi K2 0711", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-11", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.2 }, - limit: { context: 131000, output: 26215 }, - }, - "moonshotai/kimi-k2-0905": { - id: "moonshotai/kimi-k2-0905", - name: "MoonshotAI: Kimi K2 0905", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2, cache_read: 0.15 }, - limit: { context: 131072, output: 26215 }, - }, - "moonshotai/kimi-k2.5": { - id: "moonshotai/kimi-k2.5", - name: "MoonshotAI: Kimi K2.5", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-01-27", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.45, output: 2.2 }, - limit: { context: 262144, output: 65535 }, - }, - "moonshotai/kimi-k2-thinking": { - id: "moonshotai/kimi-k2-thinking", - name: "MoonshotAI: Kimi K2 Thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-11-06", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.47, output: 2, cache_read: 0.2 }, - limit: { context: 131072, output: 65535 }, - }, - "baidu/ernie-4.5-vl-424b-a47b": { - id: "baidu/ernie-4.5-vl-424b-a47b", - name: "Baidu: ERNIE 4.5 VL 424B A47B ", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-06-30", - last_updated: "2026-01", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.42, output: 1.25 }, - limit: { context: 123000, output: 16000 }, - }, - "baidu/ernie-4.5-vl-28b-a3b": { - id: "baidu/ernie-4.5-vl-28b-a3b", - name: "Baidu: ERNIE 4.5 VL 28B A3B", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-06-30", - last_updated: "2025-06-30", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.14, output: 0.56 }, - limit: { context: 30000, output: 8000 }, - }, - "baidu/ernie-4.5-21b-a3b-thinking": { - id: "baidu/ernie-4.5-21b-a3b-thinking", - name: "Baidu: ERNIE 4.5 21B A3B Thinking", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-09-19", - last_updated: "2025-09-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.28 }, - limit: { context: 131072, output: 65536 }, - }, - "baidu/ernie-4.5-300b-a47b": { - id: "baidu/ernie-4.5-300b-a47b", - name: "Baidu: ERNIE 4.5 300B A47B ", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-06-30", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.28, output: 1.1 }, - limit: { context: 123000, output: 12000 }, - }, - "baidu/ernie-4.5-21b-a3b": { - id: "baidu/ernie-4.5-21b-a3b", - name: "Baidu: ERNIE 4.5 21B A3B", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-06-30", - last_updated: "2025-06-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.28 }, - limit: { context: 120000, output: 8000 }, - }, - "google/gemini-2.5-flash-lite-preview-09-2025": { - id: "google/gemini-2.5-flash-lite-preview-09-2025", - name: "Google: Gemini 2.5 Flash Lite Preview 09-2025", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-09-25", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, reasoning: 0.4, cache_read: 0.01, cache_write: 0.083333 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-3.1-pro-preview-customtools": { - id: "google/gemini-3.1-pro-preview-customtools", - name: "Google: Gemini 3.1 Pro Preview Custom Tools", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-26", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, reasoning: 12 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-2.5-pro-preview-05-06": { - id: "google/gemini-2.5-pro-preview-05-06", - name: "Google: Gemini 2.5 Pro Preview 05-06", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-05-06", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, reasoning: 10, cache_read: 0.125, cache_write: 0.375 }, - limit: { context: 1048576, output: 65535 }, - }, - "google/gemini-2.5-flash": { - id: "google/gemini-2.5-flash", - name: "Google: Gemini 2.5 Flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-17", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5, reasoning: 2.5, cache_read: 0.03, cache_write: 0.083333 }, - limit: { context: 1048576, output: 65535 }, - }, - "google/gemini-2.5-pro-preview": { - id: "google/gemini-2.5-pro-preview", - name: "Google: Gemini 2.5 Pro Preview 06-05", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-06-05", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, reasoning: 10, cache_read: 0.125, cache_write: 0.375 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-3.1-flash-image-preview": { - id: "google/gemini-3.1-flash-image-preview", - name: "Google: Nano Banana 2 (Gemini 3.1 Flash Image Preview)", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2026-02-26", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["image", "text"] }, - open_weights: false, - cost: { input: 0.5, output: 3 }, - limit: { context: 65536, output: 65536 }, - }, - "google/gemini-2.0-flash-001": { - id: "google/gemini-2.0-flash-001", - name: "Google: Gemini 2.0 Flash", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-11", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025, cache_write: 0.083333 }, - limit: { context: 1048576, output: 8192 }, - }, - "google/gemini-3.1-flash-lite-preview": { - id: "google/gemini-3.1-flash-lite-preview", - name: "Google: Gemini 3.1 Flash Lite Preview", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-03", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1.5, reasoning: 1.5 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-3-flash-preview": { - id: "google/gemini-3-flash-preview", - name: "Google: Gemini 3 Flash Preview", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-17", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 3, reasoning: 3, cache_read: 0.05, cache_write: 0.083333 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemma-2-27b-it": { - id: "google/gemma-2-27b-it", - name: "Google: Gemma 2 27B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-06-24", - last_updated: "2024-06-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.65, output: 0.65 }, - limit: { context: 8192, output: 2048 }, - }, - "google/gemini-2.5-flash-lite": { - id: "google/gemini-2.5-flash-lite", - name: "Google: Gemini 2.5 Flash Lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-06-17", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, reasoning: 0.4, cache_read: 0.01, cache_write: 0.083333 }, - limit: { context: 1048576, output: 65535 }, - }, - "google/gemini-3.1-pro-preview": { - id: "google/gemini-3.1-pro-preview", - name: "Google: Gemini 3.1 Pro Preview", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-19", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, reasoning: 12 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemini-2.0-flash-lite-001": { - id: "google/gemini-2.0-flash-lite-001", - name: "Google: Gemini 2.0 Flash Lite", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-11", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 1048576, output: 8192 }, - }, - "google/gemini-2.5-flash-image": { - id: "google/gemini-2.5-flash-image", - name: "Google: Nano Banana (Gemini 2.5 Flash Image)", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-10-08", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["image", "text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5 }, - limit: { context: 32768, output: 32768 }, - }, - "google/gemini-3-pro-image-preview": { - id: "google/gemini-3-pro-image-preview", - name: "Google: Nano Banana Pro (Gemini 3 Pro Image Preview)", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-11-20", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["image", "text"] }, - open_weights: false, - cost: { input: 2, output: 12, reasoning: 12 }, - limit: { context: 65536, output: 32768 }, - }, - "google/gemma-2-9b-it": { - id: "google/gemma-2-9b-it", - name: "Google: Gemma 2 9B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-06-28", - last_updated: "2024-06-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.09 }, - limit: { context: 8192, output: 1639 }, - }, - "google/gemma-3n-e4b-it": { - id: "google/gemma-3n-e4b-it", - name: "Google: Gemma 3n 4B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-05-20", - last_updated: "2025-05-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.04 }, - limit: { context: 32768, output: 6554 }, - }, - "google/gemini-3-pro-preview": { - id: "google/gemini-3-pro-preview", - name: "Google: Gemini 3 Pro Preview", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-11-18", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12, reasoning: 12, cache_read: 0.2, cache_write: 0.375 }, - limit: { context: 1048576, output: 65536 }, - }, - "google/gemma-3-12b-it": { - id: "google/gemma-3-12b-it", - name: "Google: Gemma 3 12B", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-03-13", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.13, cache_read: 0.015 }, - limit: { context: 131072, output: 131072 }, - }, - "google/gemma-3-4b-it": { - id: "google/gemma-3-4b-it", - name: "Google: Gemma 3 4B", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-03-13", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.08 }, - limit: { context: 131072, output: 19200 }, - }, - "google/gemma-3-27b-it": { - id: "google/gemma-3-27b-it", - name: "Google: Gemma 3 27B", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-03-12", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.11, cache_read: 0.02 }, - limit: { context: 128000, output: 65536 }, - }, - "google/gemini-2.5-pro": { - id: "google/gemini-2.5-pro", - name: "Google: Gemini 2.5 Pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-03-20", - last_updated: "2026-03-15", - modalities: { input: ["audio", "image", "pdf", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, reasoning: 10, cache_read: 0.125, cache_write: 0.375 }, - limit: { context: 1048576, output: 65536 }, - }, - "z-ai/glm-5": { - id: "z-ai/glm-5", - name: "Z.ai: GLM 5", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.72, output: 2.3 }, - limit: { context: 202752, output: 131072 }, - }, - "z-ai/glm-4.5-air": { - id: "z-ai/glm-4.5-air", - name: "Z.ai: GLM 4.5 Air", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.85, cache_read: 0.025 }, - limit: { context: 131072, output: 98304 }, - }, - "z-ai/glm-4.5": { - id: "z-ai/glm-4.5", - name: "Z.ai: GLM 4.5", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-28", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.2, cache_read: 0.175 }, - limit: { context: 131072, output: 98304 }, - }, - "z-ai/glm-4.7-flash": { - id: "z-ai/glm-4.7-flash", - name: "Z.ai: GLM 4.7 Flash", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.4, cache_read: 0.01 }, - limit: { context: 202752, output: 40551 }, - }, - "z-ai/glm-4.6": { - id: "z-ai/glm-4.6", - name: "Z.ai: GLM 4.6", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-09-30", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.39, output: 1.9, cache_read: 0.175 }, - limit: { context: 204800, output: 204800 }, - }, - "z-ai/glm-4.7": { - id: "z-ai/glm-4.7", - name: "Z.ai: GLM 4.7", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-22", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.38, output: 1.98, cache_read: 0.2 }, - limit: { context: 202752, output: 65535 }, - }, - "z-ai/glm-4-32b": { - id: "z-ai/glm-4-32b", - name: "Z.ai: GLM 4 32B ", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-25", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 128000, output: 32768 }, - }, - "z-ai/glm-4.5v": { - id: "z-ai/glm-4.5v", - name: "Z.ai: GLM 4.5V", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-11", - last_updated: "2025-08-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 1.8, cache_read: 0.11 }, - limit: { context: 65536, output: 16384 }, - }, - "z-ai/glm-4.6v": { - id: "z-ai/glm-4.6v", - name: "Z.ai: GLM 4.6V", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-09-30", - last_updated: "2026-01-10", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 131072, output: 131072 }, - }, - "deepcogito/cogito-v2.1-671b": { - id: "deepcogito/cogito-v2.1-671b", - name: "Deep Cogito: Cogito v2.1 671B", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-11-14", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.25, output: 1.25 }, - limit: { context: 128000, output: 32768 }, - }, - "meituan/longcat-flash-chat": { - id: "meituan/longcat-flash-chat", - name: "Meituan: LongCat Flash Chat", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-08-30", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.8, cache_read: 0.2 }, - limit: { context: 131072, output: 131072 }, - }, - "bytedance/ui-tars-1.5-7b": { - id: "bytedance/ui-tars-1.5-7b", - name: "ByteDance: UI-TARS 7B ", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-07-23", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.2 }, - limit: { context: 128000, output: 2048 }, - }, - "undi95/remm-slerp-l2-13b": { - id: "undi95/remm-slerp-l2-13b", - name: "ReMM SLERP 13B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2023-07-22", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.45, output: 0.65 }, - limit: { context: 6144, output: 4096 }, - }, - "qwen/qwen3.5-27b": { - id: "qwen/qwen3.5-27b", - name: "Qwen: Qwen3.5-27B", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-26", - last_updated: "2026-03-15", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.195, output: 1.56 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen/qwen-vl-plus": { - id: "qwen/qwen-vl-plus", - name: "Qwen: Qwen VL Plus", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-01-25", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1365, output: 0.4095, cache_read: 0.042 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen/qwen-vl-max": { - id: "qwen/qwen-vl-max", - name: "Qwen: Qwen VL Max", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-04-08", - last_updated: "2025-08-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 3.2 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen3-next-80b-a3b-thinking": { - id: "qwen/qwen3-next-80b-a3b-thinking", - name: "Qwen: Qwen3 Next 80B A3B Thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-09-11", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.0975, output: 0.78 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen-2.5-vl-7b-instruct": { - id: "qwen/qwen-2.5-vl-7b-instruct", - name: "Qwen: Qwen2.5-VL 7B Instruct", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-08-28", - last_updated: "2024-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 32768, output: 6554 }, - }, - "qwen/qwen3-max-thinking": { - id: "qwen/qwen3-max-thinking", - name: "Qwen: Qwen3 Max Thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-01-23", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.78, output: 3.9 }, - limit: { context: 262144, output: 32768 }, - }, - "qwen/qwen3-14b": { - id: "qwen/qwen3-14b", - name: "Qwen: Qwen3 14B", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-04", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.24, cache_read: 0.025 }, - limit: { context: 40960, output: 40960 }, - }, - "qwen/qwen3.5-35b-a3b": { - id: "qwen/qwen3.5-35b-a3b", - name: "Qwen: Qwen3.5-35B-A3B", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-26", - last_updated: "2026-03-15", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1625, output: 1.3 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen/qwq-32b": { - id: "qwen/qwq-32b", - name: "Qwen: QwQ 32B", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2024-11-28", - last_updated: "2025-04-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.4 }, - limit: { context: 32768, output: 32768 }, - }, - "qwen/qwen3-coder-flash": { - id: "qwen/qwen3-coder-flash", - name: "Qwen: Qwen3 Coder Flash", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-23", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.195, output: 0.975, cache_read: 0.06 }, - limit: { context: 1000000, output: 65536 }, - }, - "qwen/qwen3-vl-8b-thinking": { - id: "qwen/qwen3-vl-8b-thinking", - name: "Qwen: Qwen3 VL 8B Thinking", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-10-15", - last_updated: "2025-11-25", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.117, output: 1.365 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen2.5-vl-32b-instruct": { - id: "qwen/qwen2.5-vl-32b-instruct", - name: "Qwen: Qwen2.5 VL 32B Instruct", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-03-24", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.6, cache_read: 0.025 }, - limit: { context: 128000, output: 16384 }, - }, - "qwen/qwen-max": { - id: "qwen/qwen-max", - name: "Qwen: Qwen-Max ", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-04-03", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.04, output: 4.16, cache_read: 0.32 }, - limit: { context: 32768, output: 8192 }, - }, - "qwen/qwen2.5-coder-7b-instruct": { - id: "qwen/qwen2.5-coder-7b-instruct", - name: "Qwen: Qwen2.5 Coder 7B Instruct", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-09-17", - last_updated: "2024-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.09 }, - limit: { context: 32768, output: 6554 }, - }, - "qwen/qwen3-coder-next": { - id: "qwen/qwen3-coder-next", - name: "Qwen: Qwen3 Coder Next", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2026-02-02", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.12, output: 0.75, cache_read: 0.035 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen/qwen-turbo": { - id: "qwen/qwen-turbo", - name: "Qwen: Qwen-Turbo", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-11-01", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.0325, output: 0.13, cache_read: 0.01 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen/qwen3-coder": { - id: "qwen/qwen3-coder", - name: "Qwen: Qwen3 Coder 480B A35B", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.22, output: 1, cache_read: 0.022 }, - limit: { context: 262144, output: 52429 }, - }, - "qwen/qwen3-8b": { - id: "qwen/qwen3-8b", - name: "Qwen: Qwen3 8B", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-04", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.4, cache_read: 0.05 }, - limit: { context: 40960, output: 8192 }, - }, - "qwen/qwen3-32b": { - id: "qwen/qwen3-32b", - name: "Qwen: Qwen3 32B", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.08, output: 0.24, cache_read: 0.04 }, - limit: { context: 40960, output: 40960 }, - }, - "qwen/qwen3-235b-a22b-2507": { - id: "qwen/qwen3-235b-a22b-2507", - name: "Qwen: Qwen3 235B A22B Instruct 2507", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-04", - last_updated: "2026-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.071, output: 0.1 }, - limit: { context: 262144, output: 52429 }, - }, - "qwen/qwen3.5-397b-a17b": { - id: "qwen/qwen3.5-397b-a17b", - name: "Qwen: Qwen3.5 397B A17B", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-15", - last_updated: "2026-03-15", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.39, output: 2.34 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen/qwen-2.5-7b-instruct": { - id: "qwen/qwen-2.5-7b-instruct", - name: "Qwen: Qwen2.5 7B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-09", - last_updated: "2025-04-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.1 }, - limit: { context: 32768, output: 6554 }, - }, - "qwen/qwen-2.5-coder-32b-instruct": { - id: "qwen/qwen-2.5-coder-32b-instruct", - name: "Qwen2.5 Coder 32B Instruct", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-11-11", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.2, cache_read: 0.015 }, - limit: { context: 32768, output: 8192 }, - }, - "qwen/qwen3.5-plus-02-15": { - id: "qwen/qwen3.5-plus-02-15", - name: "Qwen: Qwen3.5 Plus 2026-02-15", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-15", - last_updated: "2026-03-15", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.26, output: 1.56 }, - limit: { context: 1000000, output: 65536 }, - }, - "qwen/qwen3-30b-a3b-instruct-2507": { - id: "qwen/qwen3-30b-a3b-instruct-2507", - name: "Qwen: Qwen3 30B A3B Instruct 2507", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-29", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.3, cache_read: 0.04 }, - limit: { context: 262144, output: 262144 }, - }, - "qwen/qwen2.5-vl-72b-instruct": { - id: "qwen/qwen2.5-vl-72b-instruct", - name: "Qwen: Qwen2.5 VL 72B Instruct", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-02-01", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.8, output: 0.8, cache_read: 0.075 }, - limit: { context: 32768, output: 32768 }, - }, - "qwen/qwen3-235b-a22b": { - id: "qwen/qwen3-235b-a22b", - name: "Qwen: Qwen3 235B A22B", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.455, output: 1.82, cache_read: 0.15 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen/qwen3-coder-30b-a3b-instruct": { - id: "qwen/qwen3-coder-30b-a3b-instruct", - name: "Qwen: Qwen3 Coder 30B A3B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-31", - last_updated: "2025-07-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.27 }, - limit: { context: 160000, output: 32768 }, - }, - "qwen/qwen3-vl-235b-a22b-instruct": { - id: "qwen/qwen3-vl-235b-a22b-instruct", - name: "Qwen: Qwen3 VL 235B A22B Instruct", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-09-23", - last_updated: "2026-01-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.88, cache_read: 0.11 }, - limit: { context: 262144, output: 52429 }, - }, - "qwen/qwen-2.5-72b-instruct": { - id: "qwen/qwen-2.5-72b-instruct", - name: "Qwen2.5 72B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-09", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.12, output: 0.39 }, - limit: { context: 32768, output: 16384 }, - }, - "qwen/qwen3-vl-30b-a3b-thinking": { - id: "qwen/qwen3-vl-30b-a3b-thinking", - name: "Qwen: Qwen3 VL 30B A3B Thinking", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-10-11", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 1.56 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen3-vl-235b-a22b-thinking": { - id: "qwen/qwen3-vl-235b-a22b-thinking", - name: "Qwen: Qwen3 VL 235B A22B Thinking", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-09-24", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.26, output: 2.6 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen3-30b-a3b-thinking-2507": { - id: "qwen/qwen3-30b-a3b-thinking-2507", - name: "Qwen: Qwen3 30B A3B Thinking 2507", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-29", - last_updated: "2025-07-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.051, output: 0.34 }, - limit: { context: 32768, output: 6554 }, - }, - "qwen/qwen-plus": { - id: "qwen/qwen-plus", - name: "Qwen: Qwen-Plus", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-01-25", - last_updated: "2025-09-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.2, cache_read: 0.08 }, - limit: { context: 1000000, output: 32768 }, - }, - "qwen/qwen3-235b-a22b-thinking-2507": { - id: "qwen/qwen3-235b-a22b-thinking-2507", - name: "Qwen: Qwen3 235B A22B Thinking 2507", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-25", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.11, output: 0.6 }, - limit: { context: 262144, output: 262144 }, - }, - "qwen/qwen3.5-9b": { - id: "qwen/qwen3.5-9b", - name: "Qwen: Qwen3.5-9B", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-10", - last_updated: "2026-03-15", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.15 }, - limit: { context: 256000, output: 32768 }, - }, - "qwen/qwen-plus-2025-07-28": { - id: "qwen/qwen-plus-2025-07-28", - name: "Qwen: Qwen Plus 0728", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-09-09", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.26, output: 0.78 }, - limit: { context: 1000000, output: 32768 }, - }, - "qwen/qwen3-vl-30b-a3b-instruct": { - id: "qwen/qwen3-vl-30b-a3b-instruct", - name: "Qwen: Qwen3 VL 30B A3B Instruct", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-10-05", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.52 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen3-next-80b-a3b-instruct": { - id: "qwen/qwen3-next-80b-a3b-instruct", - name: "Qwen: Qwen3 Next 80B A3B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-09-11", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 1.1 }, - limit: { context: 131072, output: 52429 }, - }, - "qwen/qwen3-vl-32b-instruct": { - id: "qwen/qwen3-vl-32b-instruct", - name: "Qwen: Qwen3 VL 32B Instruct", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-10-21", - last_updated: "2025-11-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.104, output: 0.416 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen3-vl-8b-instruct": { - id: "qwen/qwen3-vl-8b-instruct", - name: "Qwen: Qwen3 VL 8B Instruct", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-10-15", - last_updated: "2025-11-25", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.08, output: 0.5 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen/qwen3.5-122b-a10b": { - id: "qwen/qwen3.5-122b-a10b", - name: "Qwen: Qwen3.5-122B-A10B", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-26", - last_updated: "2026-03-15", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.26, output: 2.08 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen/qwen3-max": { - id: "qwen/qwen3-max", - name: "Qwen: Qwen3 Max", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-09-05", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.2, output: 6, cache_read: 0.24 }, - limit: { context: 262144, output: 32768 }, - }, - "qwen/qwen3-30b-a3b": { - id: "qwen/qwen3-30b-a3b", - name: "Qwen: Qwen3 30B A3B", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-04", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.08, output: 0.28, cache_read: 0.03 }, - limit: { context: 40960, output: 40960 }, - }, - "qwen/qwen3-coder-plus": { - id: "qwen/qwen3-coder-plus", - name: "Qwen: Qwen3 Coder Plus", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-01", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.65, output: 3.25, cache_read: 0.2 }, - limit: { context: 1000000, output: 65536 }, - }, - "qwen/qwen-plus-2025-07-28:thinking": { - id: "qwen/qwen-plus-2025-07-28:thinking", - name: "Qwen: Qwen Plus 0728 (thinking)", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-09-09", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.26, output: 0.78 }, - limit: { context: 1000000, output: 32768 }, - }, - "qwen/qwen3.5-flash-02-23": { - id: "qwen/qwen3.5-flash-02-23", - name: "Qwen: Qwen3.5-Flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-26", - last_updated: "2026-03-15", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 1000000, output: 65536 }, - }, - "eleutherai/llemma_7b": { - id: "eleutherai/llemma_7b", - name: "EleutherAI: Llemma 7b", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-14", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.8, output: 1.2 }, - limit: { context: 4096, output: 4096 }, - }, - "x-ai/grok-3": { - id: "x-ai/grok-3", - name: "xAI: Grok 3", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75 }, - limit: { context: 131072, output: 26215 }, - }, - "x-ai/grok-code-fast-1": { - id: "x-ai/grok-code-fast-1", - name: "xAI: Grok Code Fast 1", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-26", - last_updated: "2025-08-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.5, cache_read: 0.02 }, - limit: { context: 256000, output: 10000 }, - }, - "x-ai/grok-4-fast": { - id: "x-ai/grok-4-fast", - name: "xAI: Grok 4 Fast", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-19", - last_updated: "2025-08-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "x-ai/grok-4": { - id: "x-ai/grok-4", - name: "xAI: Grok 4", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75 }, - limit: { context: 256000, output: 51200 }, - }, - "x-ai/grok-4.1-fast": { - id: "x-ai/grok-4.1-fast", - name: "xAI: Grok 4.1 Fast", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "x-ai/grok-3-mini-beta": { - id: "x-ai/grok-3-mini-beta", - name: "xAI: Grok 3 Mini Beta", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.5, cache_read: 0.075 }, - limit: { context: 131072, output: 26215 }, - }, - "x-ai/grok-3-mini": { - id: "x-ai/grok-3-mini", - name: "xAI: Grok 3 Mini", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.5, cache_read: 0.075 }, - limit: { context: 131072, output: 26215 }, - }, - "x-ai/grok-code-fast-1:optimized:free": { - id: "x-ai/grok-code-fast-1:optimized:free", - name: "xAI: Grok Code Fast 1 Optimized (experimental, free)", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-27", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 256000, output: 10000 }, - }, - "x-ai/grok-4.20-multi-agent-beta": { - id: "x-ai/grok-4.20-multi-agent-beta", - name: "xAI: Grok 4.20 Multi-Agent Beta", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2026-03-12", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6 }, - limit: { context: 2000000, output: 32768 }, - }, - "x-ai/grok-4.20-beta": { - id: "x-ai/grok-4.20-beta", - name: "xAI: Grok 4.20 Beta", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-12", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6 }, - limit: { context: 2000000, output: 32768 }, - }, - "x-ai/grok-3-beta": { - id: "x-ai/grok-3-beta", - name: "xAI: Grok 3 Beta", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75 }, - limit: { context: 131072, output: 26215 }, - }, - "meta-llama/llama-4-scout": { - id: "meta-llama/llama-4-scout", - name: "Meta: Llama 4 Scout", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.08, output: 0.3 }, - limit: { context: 327680, output: 16384 }, - }, - "meta-llama/llama-3.1-70b-instruct": { - id: "meta-llama/llama-3.1-70b-instruct", - name: "Meta: Llama 3.1 70B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-07-16", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 0.4 }, - limit: { context: 131072, output: 26215 }, - }, - "meta-llama/llama-3.3-70b-instruct": { - id: "meta-llama/llama-3.3-70b-instruct", - name: "Meta: Llama 3.3 70B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-08-01", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.32 }, - limit: { context: 131072, output: 16384 }, - }, - "meta-llama/llama-3-70b-instruct": { - id: "meta-llama/llama-3-70b-instruct", - name: "Meta: Llama 3 70B Instruct", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.51, output: 0.74 }, - limit: { context: 8192, output: 8000 }, - }, - "meta-llama/llama-3.2-11b-vision-instruct": { - id: "meta-llama/llama-3.2-11b-vision-instruct", - name: "Meta: Llama 3.2 11B Vision Instruct", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.049, output: 0.049 }, - limit: { context: 131072, output: 16384 }, - }, - "meta-llama/llama-3.2-3b-instruct": { - id: "meta-llama/llama-3.2-3b-instruct", - name: "Meta: Llama 3.2 3B Instruct", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-09-18", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.051, output: 0.34 }, - limit: { context: 80000, output: 16384 }, - }, - "meta-llama/llama-guard-3-8b": { - id: "meta-llama/llama-guard-3-8b", - name: "Llama Guard 3 8B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-04-18", - last_updated: "2026-02-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.06 }, - limit: { context: 131072, output: 26215 }, - }, - "meta-llama/llama-3.2-1b-instruct": { - id: "meta-llama/llama-3.2-1b-instruct", - name: "Meta: Llama 3.2 1B Instruct", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-09-18", - last_updated: "2026-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.027, output: 0.2 }, - limit: { context: 60000, output: 12000 }, - }, - "meta-llama/llama-3.1-405b-instruct": { - id: "meta-llama/llama-3.1-405b-instruct", - name: "Meta: Llama 3.1 405B Instruct", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-07-16", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 4, output: 4 }, - limit: { context: 131000, output: 26200 }, - }, - "meta-llama/llama-4-maverick": { - id: "meta-llama/llama-4-maverick", - name: "Meta: Llama 4 Maverick", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-04-05", - last_updated: "2025-12-24", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 1048576, output: 16384 }, - }, - "meta-llama/llama-3.1-8b-instruct": { - id: "meta-llama/llama-3.1-8b-instruct", - name: "Meta: Llama 3.1 8B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-07-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.05 }, - limit: { context: 16384, output: 16384 }, - }, - "meta-llama/llama-guard-4-12b": { - id: "meta-llama/llama-guard-4-12b", - name: "Meta: Llama Guard 4 12B", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.18, output: 0.18 }, - limit: { context: 163840, output: 32768 }, - }, - "meta-llama/llama-3-8b-instruct": { - id: "meta-llama/llama-3-8b-instruct", - name: "Meta: Llama 3 8B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-04-25", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.04 }, - limit: { context: 8192, output: 16384 }, - }, - "meta-llama/llama-3.1-405b": { - id: "meta-llama/llama-3.1-405b", - name: "Meta: Llama 3.1 405B (base)", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-08-02", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 4, output: 4 }, - limit: { context: 32768, output: 32768 }, - }, - "tngtech/deepseek-r1t2-chimera": { - id: "tngtech/deepseek-r1t2-chimera", - name: "TNG: DeepSeek R1T2 Chimera", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-07-08", - last_updated: "2025-07-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 0.85, cache_read: 0.125 }, - limit: { context: 163840, output: 163840 }, - }, - "mistralai/voxtral-small-24b-2507": { - id: "mistralai/voxtral-small-24b-2507", - name: "Mistral: Voxtral Small 24B 2507", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-01", - last_updated: "2025-07-01", - modalities: { input: ["text", "audio"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 32000, output: 6400 }, - }, - "mistralai/ministral-3b-2512": { - id: "mistralai/ministral-3b-2512", - name: "Mistral: Ministral 3 3B 2512", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-12-02", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 131072, output: 32768 }, - }, - "mistralai/mistral-saba": { - id: "mistralai/mistral-saba", - name: "Mistral: Saba", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-02-17", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 32768, output: 32768 }, - }, - "mistralai/mistral-medium-3": { - id: "mistralai/mistral-medium-3", - name: "Mistral: Mistral Medium 3", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-05-07", - last_updated: "2025-05-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 131072, output: 26215 }, - }, - "mistralai/mistral-small-24b-instruct-2501": { - id: "mistralai/mistral-small-24b-instruct-2501", - name: "Mistral: Mistral Small 3", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.08 }, - limit: { context: 32768, output: 16384 }, - }, - "mistralai/codestral-2508": { - id: "mistralai/codestral-2508", - name: "Mistral: Codestral 2508", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-08-01", - last_updated: "2025-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 256000, output: 51200 }, - }, - "mistralai/pixtral-large-2411": { - id: "mistralai/pixtral-large-2411", - name: "Mistral: Pixtral Large 2411", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-11-19", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 6 }, - limit: { context: 131072, output: 32768 }, - }, - "mistralai/mistral-small-3.1-24b-instruct": { - id: "mistralai/mistral-small-3.1-24b-instruct", - name: "Mistral: Mistral Small 3.1 24B", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-03-17", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.35, output: 0.56, cache_read: 0.015 }, - limit: { context: 128000, output: 131072 }, - }, - "mistralai/mistral-small-creative": { - id: "mistralai/mistral-small-creative", - name: "Mistral: Mistral Small Creative", - attachment: false, - reasoning: false, - tool_call: true, - release_date: "2025-12-17", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 32768, output: 32768 }, - }, - "mistralai/mistral-large-2512": { - id: "mistralai/mistral-large-2512", - name: "Mistral: Mistral Large 3 2512", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-11-01", - last_updated: "2025-12-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 262144, output: 52429 }, - }, - "mistralai/ministral-8b-2512": { - id: "mistralai/ministral-8b-2512", - name: "Mistral: Ministral 3 8B 2512", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-12-02", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 262144, output: 32768 }, - }, - "mistralai/ministral-14b-2512": { - id: "mistralai/ministral-14b-2512", - name: "Mistral: Ministral 3 14B 2512", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-12-16", - last_updated: "2025-12-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 262144, output: 52429 }, - }, - "mistralai/devstral-medium": { - id: "mistralai/devstral-medium", - name: "Mistral: Devstral Medium", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-07-10", - last_updated: "2025-07-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2 }, - limit: { context: 131072, output: 26215 }, - }, - "mistralai/mistral-large-2407": { - id: "mistralai/mistral-large-2407", - name: "Mistral Large 2407", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-11-19", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 6 }, - limit: { context: 131072, output: 32768 }, - }, - "mistralai/mistral-nemo": { - id: "mistralai/mistral-nemo", - name: "Mistral: Mistral Nemo", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-07-01", - last_updated: "2024-07-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.04 }, - limit: { context: 131072, output: 16384 }, - }, - "mistralai/devstral-2512": { - id: "mistralai/devstral-2512", - name: "Mistral: Devstral 2 2512", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-09-12", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2, cache_read: 0.025 }, - limit: { context: 262144, output: 65536 }, - }, - "mistralai/devstral-small": { - id: "mistralai/devstral-small", - name: "Mistral: Devstral Small 1.1", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-05-07", - last_updated: "2025-07-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 131072, output: 26215 }, - }, - "mistralai/mistral-small-3.2-24b-instruct": { - id: "mistralai/mistral-small-3.2-24b-instruct", - name: "Mistral: Mistral Small 3.2 24B", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-06-20", - last_updated: "2025-06-20", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.18, cache_read: 0.03 }, - limit: { context: 131072, output: 131072 }, - }, - "mistralai/mixtral-8x22b-instruct": { - id: "mistralai/mixtral-8x22b-instruct", - name: "Mistral: Mixtral 8x22B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-04-17", - last_updated: "2024-04-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 6 }, - limit: { context: 65536, output: 13108 }, - }, - "mistralai/mistral-large-2411": { - id: "mistralai/mistral-large-2411", - name: "Mistral Large 2411", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-07-24", - last_updated: "2024-11-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 6 }, - limit: { context: 131072, output: 26215 }, - }, - "mistralai/mistral-7b-instruct-v0.1": { - id: "mistralai/mistral-7b-instruct-v0.1", - name: "Mistral: Mistral 7B Instruct v0.1", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.11, output: 0.19 }, - limit: { context: 2824, output: 565 }, - }, - "mistralai/mistral-large": { - id: "mistralai/mistral-large", - name: "Mistral Large", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-07-24", - last_updated: "2025-12-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2, output: 6 }, - limit: { context: 128000, output: 25600 }, - }, - "mistralai/mixtral-8x7b-instruct": { - id: "mistralai/mixtral-8x7b-instruct", - name: "Mistral: Mixtral 8x7B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2023-12-10", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.54, output: 0.54 }, - limit: { context: 32768, output: 16384 }, - }, - "mistralai/mistral-medium-3.1": { - id: "mistralai/mistral-medium-3.1", - name: "Mistral: Mistral Medium 3.1", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-08-12", - last_updated: "2025-08-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 131072, output: 26215 }, - }, - "openai/gpt-4o-2024-11-20": { - id: "openai/gpt-4o-2024-11-20", - name: "OpenAI: GPT-4o (2024-11-20)", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-11-20", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10, cache_read: 1.25 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5.3-codex": { - id: "openai/gpt-5.3-codex", - name: "OpenAI: GPT-5.3-Codex", - attachment: true, - reasoning: true, - tool_call: true, - release_date: "2026-02-25", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5-codex": { - id: "openai/gpt-5-codex", - name: "OpenAI: GPT-5 Codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5-pro": { - id: "openai/gpt-5-pro", - name: "OpenAI: GPT-5 Pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-10-06", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 120 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-4o-mini": { - id: "openai/gpt-4o-mini", - name: "OpenAI: GPT-4o-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-07-18", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.075 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-4o-mini-search-preview": { - id: "openai/gpt-4o-mini-search-preview", - name: "OpenAI: GPT-4o-mini Search Preview", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-01", - last_updated: "2025-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-4o:extended": { - id: "openai/gpt-4o:extended", - name: "OpenAI: GPT-4o (extended)", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-05-13", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 6, output: 18 }, - limit: { context: 128000, output: 64000 }, - }, - "openai/gpt-5.1-codex-max": { - id: "openai/gpt-5.1-codex-max", - name: "OpenAI: GPT-5.1-Codex-Max", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-4o-2024-05-13": { - id: "openai/gpt-4o-2024-05-13", - name: "OpenAI: GPT-4o (2024-05-13)", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-05-13", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 15 }, - limit: { context: 128000, output: 4096 }, - }, - "openai/gpt-4o-audio-preview": { - id: "openai/gpt-4o-audio-preview", - name: "OpenAI: GPT-4o Audio", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-08-15", - last_updated: "2026-03-15", - modalities: { input: ["audio", "text"], output: ["audio", "text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-4o-mini-2024-07-18": { - id: "openai/gpt-4o-mini-2024-07-18", - name: "OpenAI: GPT-4o-mini (2024-07-18)", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-07-18", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5.2-codex": { - id: "openai/gpt-5.2-codex", - name: "OpenAI: GPT-5.2-Codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2026-01-14", - last_updated: "2026-01-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-audio": { - id: "openai/gpt-audio", - name: "OpenAI: GPT Audio", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2026-01-20", - last_updated: "2026-03-15", - modalities: { input: ["audio", "text"], output: ["audio", "text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/o3-deep-research": { - id: "openai/o3-deep-research", - name: "OpenAI: o3 Deep Research", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2024-06-26", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 40, cache_read: 2.5 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-3.5-turbo-16k": { - id: "openai/gpt-3.5-turbo-16k", - name: "OpenAI: GPT-3.5 Turbo 16k", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2023-08-28", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 4 }, - limit: { context: 16385, output: 4096 }, - }, - "openai/o1": { - id: "openai/o1", - name: "OpenAI: o1", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-12-05", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 60, cache_read: 7.5 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-5.1": { - id: "openai/gpt-5.1", - name: "OpenAI: GPT-5.1", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-11-13", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5-image-mini": { - id: "openai/gpt-5-image-mini", - name: "OpenAI: GPT-5 Image Mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-10-16", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["image", "text"] }, - open_weights: false, - cost: { input: 2.5, output: 2 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.2-chat": { - id: "openai/gpt-5.2-chat", - name: "OpenAI: GPT-5.2 Chat", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-12-11", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/o4-mini-deep-research": { - id: "openai/o4-mini-deep-research", - name: "OpenAI: o4 Mini Deep Research", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2024-06-26", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-5-chat": { - id: "openai/gpt-5-chat", - name: "OpenAI: GPT-5 Chat", - attachment: true, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-08-07", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5.1-chat": { - id: "openai/gpt-5.1-chat", - name: "OpenAI: GPT-5.1 Chat", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-11-13", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/o3": { - id: "openai/o3", - name: "OpenAI: o3", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-04-16", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4-turbo-preview": { - id: "openai/gpt-4-turbo-preview", - name: "OpenAI: GPT-4 Turbo Preview", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-01-25", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 30 }, - limit: { context: 128000, output: 4096 }, - }, - "openai/gpt-5-image": { - id: "openai/gpt-5-image", - name: "OpenAI: GPT-5 Image", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-10-14", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["image", "text"] }, - open_weights: false, - cost: { input: 10, output: 10 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-4.1-nano": { - id: "openai/gpt-4.1-nano", - name: "OpenAI: GPT-4.1 Nano", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-04-14", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.025 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/gpt-3.5-turbo-0613": { - id: "openai/gpt-3.5-turbo-0613", - name: "OpenAI: GPT-3.5 Turbo (older v0613)", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2023-06-13", - last_updated: "2023-06-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 2 }, - limit: { context: 4095, output: 4096 }, - }, - "openai/gpt-3.5-turbo": { - id: "openai/gpt-3.5-turbo", - name: "OpenAI: GPT-3.5 Turbo", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2023-03-01", - last_updated: "2023-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 16385, output: 4096 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "OpenAI: gpt-oss-120b", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.039, output: 0.19 }, - limit: { context: 131072, output: 26215 }, - }, - "openai/gpt-5.1-codex-mini": { - id: "openai/gpt-5.1-codex-mini", - name: "OpenAI: GPT-5.1-Codex-Mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.025 }, - limit: { context: 400000, output: 100000 }, - }, - "openai/gpt-5.2": { - id: "openai/gpt-5.2", - name: "OpenAI: GPT-5.2", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-12-11", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-4.1": { - id: "openai/gpt-4.1", - name: "OpenAI: GPT-4.1", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-04-14", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/o3-pro": { - id: "openai/o3-pro", - name: "OpenAI: o3 Pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-04-16", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 20, output: 80 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4-turbo": { - id: "openai/gpt-4-turbo", - name: "OpenAI: GPT-4 Turbo", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2023-09-13", - last_updated: "2024-04-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 30 }, - limit: { context: 128000, output: 4096 }, - }, - "openai/gpt-5": { - id: "openai/gpt-5", - name: "OpenAI: GPT-5", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-08-07", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/o4-mini": { - id: "openai/o4-mini", - name: "OpenAI: o4 Mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-04-16", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.275 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4.1-mini": { - id: "openai/gpt-4.1-mini", - name: "OpenAI: GPT-4.1 Mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-04-14", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.6, cache_read: 0.1 }, - limit: { context: 1047576, output: 32768 }, - }, - "openai/gpt-4-0314": { - id: "openai/gpt-4-0314", - name: "OpenAI: GPT-4 (older v0314)", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2023-05-28", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 30, output: 60 }, - limit: { context: 8191, output: 4096 }, - }, - "openai/gpt-audio-mini": { - id: "openai/gpt-audio-mini", - name: "OpenAI: GPT Audio Mini", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2026-01-20", - last_updated: "2026-03-15", - modalities: { input: ["audio", "text"], output: ["audio", "text"] }, - open_weights: false, - cost: { input: 0.6, output: 2.4 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5.4": { - id: "openai/gpt-5.4", - name: "OpenAI: GPT-5.4", - attachment: true, - reasoning: true, - tool_call: true, - release_date: "2026-03-06", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 15 }, - limit: { context: 1050000, output: 128000 }, - }, - "openai/gpt-5.4-pro": { - id: "openai/gpt-5.4-pro", - name: "OpenAI: GPT-5.4 Pro", - attachment: true, - reasoning: true, - tool_call: true, - release_date: "2026-03-06", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 30, output: 180 }, - limit: { context: 1050000, output: 128000 }, - }, - "openai/gpt-5.3-chat": { - id: "openai/gpt-5.3-chat", - name: "OpenAI: GPT-5.3 Chat", - attachment: true, - reasoning: false, - tool_call: true, - release_date: "2026-03-04", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-4-1106-preview": { - id: "openai/gpt-4-1106-preview", - name: "OpenAI: GPT-4 Turbo (older v1106)", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2023-11-06", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 30 }, - limit: { context: 128000, output: 4096 }, - }, - "openai/gpt-oss-safeguard-20b": { - id: "openai/gpt-oss-safeguard-20b", - name: "OpenAI: gpt-oss-safeguard-20b", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-10-29", - last_updated: "2025-10-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.075, output: 0.3, cache_read: 0.037 }, - limit: { context: 131072, output: 65536 }, - }, - "openai/o1-pro": { - id: "openai/o1-pro", - name: "OpenAI: o1-pro", - attachment: true, - reasoning: true, - tool_call: false, - temperature: false, - release_date: "2025-03-19", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 150, output: 600 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-5.1-codex": { - id: "openai/gpt-5.1-codex", - name: "OpenAI: GPT-5.1-Codex", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-5.2-pro": { - id: "openai/gpt-5.2-pro", - name: "OpenAI: GPT-5.2 Pro", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-12-11", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 21, output: 168 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/o3-mini": { - id: "openai/o3-mini", - name: "OpenAI: o3 Mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2024-12-20", - last_updated: "2026-03-15", - modalities: { input: ["pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.55 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4o-2024-08-06": { - id: "openai/gpt-4o-2024-08-06", - name: "OpenAI: GPT-4o (2024-08-06)", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-08-06", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10, cache_read: 1.25 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-5-mini": { - id: "openai/gpt-5-mini", - name: "OpenAI: GPT-5 Mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-08-07", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.025 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "OpenAI: gpt-oss-20b", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.14 }, - limit: { context: 131072, output: 26215 }, - }, - "openai/gpt-4": { - id: "openai/gpt-4", - name: "OpenAI: GPT-4", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2023-03-14", - last_updated: "2024-04-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 30, output: 60 }, - limit: { context: 8191, output: 4096 }, - }, - "openai/gpt-5-nano": { - id: "openai/gpt-5-nano", - name: "OpenAI: GPT-5 Nano", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - release_date: "2025-08-07", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4, cache_read: 0.005 }, - limit: { context: 400000, output: 128000 }, - }, - "openai/gpt-3.5-turbo-instruct": { - id: "openai/gpt-3.5-turbo-instruct", - name: "OpenAI: GPT-3.5 Turbo Instruct", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2023-03-01", - last_updated: "2023-09-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 2 }, - limit: { context: 4095, output: 4096 }, - }, - "openai/o3-mini-high": { - id: "openai/o3-mini-high", - name: "OpenAI: o3 Mini High", - attachment: true, - reasoning: false, - tool_call: true, - temperature: false, - release_date: "2025-01-31", - last_updated: "2026-03-15", - modalities: { input: ["pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.55 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/o4-mini-high": { - id: "openai/o4-mini-high", - name: "OpenAI: o4 Mini High", - attachment: true, - reasoning: true, - tool_call: true, - release_date: "2025-04-17", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4 }, - limit: { context: 200000, output: 100000 }, - }, - "openai/gpt-4o": { - id: "openai/gpt-4o", - name: "OpenAI: GPT-4o", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-05-13", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10, cache_read: 1.25 }, - limit: { context: 128000, output: 16384 }, - }, - "openai/gpt-4o-search-preview": { - id: "openai/gpt-4o-search-preview", - name: "OpenAI: GPT-4o Search Preview", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2025-03-13", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 128000, output: 16384 }, - }, - "morph/morph-v3-fast": { - id: "morph/morph-v3-fast", - name: "Morph: Morph V3 Fast", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-08-15", - last_updated: "2024-08-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 1.2 }, - limit: { context: 81920, output: 38000 }, - }, - "morph/morph-v3-large": { - id: "morph/morph-v3-large", - name: "Morph: Morph V3 Large", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-08-15", - last_updated: "2024-08-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.9, output: 1.9 }, - limit: { context: 262144, output: 131072 }, - }, - "cohere/command-r-08-2024": { - id: "cohere/command-r-08-2024", - name: "Cohere: Command R (08-2024)", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-08-30", - last_updated: "2024-08-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 4000 }, - }, - "cohere/command-r-plus-08-2024": { - id: "cohere/command-r-plus-08-2024", - name: "Cohere: Command R+ (08-2024)", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-08-30", - last_updated: "2024-08-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.5, output: 10 }, - limit: { context: 128000, output: 4000 }, - }, - "cohere/command-r7b-12-2024": { - id: "cohere/command-r7b-12-2024", - name: "Cohere: Command R7B (12-2024)", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-02-27", - last_updated: "2024-02-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.0375, output: 0.15 }, - limit: { context: 128000, output: 4000 }, - }, - "cohere/command-a": { - id: "cohere/command-a", - name: "Cohere: Command A", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-03-13", - last_updated: "2025-03-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.5, output: 10 }, - limit: { context: 256000, output: 8192 }, - }, - "minimax/minimax-m1": { - id: "minimax/minimax-m1", - name: "MiniMax: MiniMax M1", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 2.2 }, - limit: { context: 1000000, output: 40000 }, - }, - "minimax/minimax-01": { - id: "minimax/minimax-01", - name: "MiniMax: MiniMax-01", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-01-15", - last_updated: "2025-01-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 1.1 }, - limit: { context: 1000192, output: 1000192 }, - }, - "minimax/minimax-m2.1": { - id: "minimax/minimax-m2.1", - name: "MiniMax: MiniMax M2.1", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 0.95, cache_read: 0.03 }, - limit: { context: 196608, output: 39322 }, - }, - "minimax/minimax-m2-her": { - id: "minimax/minimax-m2-her", - name: "MiniMax: MiniMax M2-her", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2026-01-23", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 65536, output: 2048 }, - }, - "minimax/minimax-m2.5:free": { - id: "minimax/minimax-m2.5:free", - name: "MiniMax: MiniMax M2.5 (free)", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0, cache_read: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "minimax/minimax-m2": { - id: "minimax/minimax-m2", - name: "MiniMax: MiniMax M2", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-10-23", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.255, output: 1, cache_read: 0.03 }, - limit: { context: 196608, output: 196608 }, - }, - "minimax/minimax-m2.5": { - id: "minimax/minimax-m2.5", - name: "MiniMax: MiniMax M2.5", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 1.2, cache_read: 0.029 }, - limit: { context: 196608, output: 196608 }, - }, - "sao10k/l3.1-70b-hanami-x1": { - id: "sao10k/l3.1-70b-hanami-x1", - name: "Sao10K: Llama 3.1 70B Hanami x1", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-01-08", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 3, output: 3 }, - limit: { context: 16000, output: 16000 }, - }, - "sao10k/l3-lunaris-8b": { - id: "sao10k/l3-lunaris-8b", - name: "Sao10K: Llama 3 8B Lunaris", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-08-13", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.05 }, - limit: { context: 8192, output: 8192 }, - }, - "sao10k/l3.1-euryale-70b": { - id: "sao10k/l3.1-euryale-70b", - name: "Sao10K: Llama 3.1 Euryale 70B v2.2", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-08-28", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.85, output: 0.85 }, - limit: { context: 131072, output: 16384 }, - }, - "sao10k/l3-euryale-70b": { - id: "sao10k/l3-euryale-70b", - name: "Sao10k: Llama 3 Euryale 70B v2.1", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-06-18", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.48, output: 1.48 }, - limit: { context: 8192, output: 8192 }, - }, - "sao10k/l3.3-euryale-70b": { - id: "sao10k/l3.3-euryale-70b", - name: "Sao10K: Llama 3.3 Euryale 70B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-12-18", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.65, output: 0.75 }, - limit: { context: 131072, output: 16384 }, - }, - "writer/palmyra-x5": { - id: "writer/palmyra-x5", - name: "Writer: Palmyra X5", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2025-04-28", - last_updated: "2025-04-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 6 }, - limit: { context: 1040000, output: 8192 }, - }, - "perplexity/sonar-reasoning-pro": { - id: "perplexity/sonar-reasoning-pro", - name: "Perplexity: Sonar Reasoning Pro", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2024-01-01", - last_updated: "2025-09-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8 }, - limit: { context: 128000, output: 25600 }, - }, - "perplexity/sonar": { - id: "perplexity/sonar", - name: "Perplexity: Sonar", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-01-01", - last_updated: "2025-09-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 1 }, - limit: { context: 127072, output: 25415 }, - }, - "perplexity/sonar-deep-research": { - id: "perplexity/sonar-deep-research", - name: "Perplexity: Sonar Deep Research", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-01-27", - last_updated: "2025-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8 }, - limit: { context: 128000, output: 25600 }, - }, - "perplexity/sonar-pro": { - id: "perplexity/sonar-pro", - name: "Perplexity: Sonar Pro", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-01-01", - last_updated: "2025-09-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, output: 8000 }, - }, - "perplexity/sonar-pro-search": { - id: "perplexity/sonar-pro-search", - name: "Perplexity: Sonar Pro Search", - attachment: true, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-10-31", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, output: 8000 }, - }, - "bytedance-seed/seed-2.0-mini": { - id: "bytedance-seed/seed-2.0-mini", - name: "ByteDance Seed: Seed-2.0-Mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-27", - last_updated: "2026-03-15", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 262144, output: 131072 }, - }, - "bytedance-seed/seed-1.6": { - id: "bytedance-seed/seed-1.6", - name: "ByteDance Seed: Seed 1.6", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-09", - last_updated: "2025-09", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2 }, - limit: { context: 262144, output: 32768 }, - }, - "bytedance-seed/seed-1.6-flash": { - id: "bytedance-seed/seed-1.6-flash", - name: "ByteDance Seed: Seed 1.6 Flash", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-12-23", - last_updated: "2026-03-15", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 262144, output: 32768 }, - }, - "bytedance-seed/seed-2.0-lite": { - id: "bytedance-seed/seed-2.0-lite", - name: "ByteDance Seed: Seed-2.0-Lite", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-10", - last_updated: "2026-03-15", - modalities: { input: ["image", "text", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 2 }, - limit: { context: 262144, output: 131072 }, - }, - "anthropic/claude-3.5-sonnet": { - id: "anthropic/claude-3.5-sonnet", - name: "Anthropic: Claude 3.5 Sonnet", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-10-22", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 6, output: 30 }, - limit: { context: 200000, output: 8192 }, - }, - "anthropic/claude-3.7-sonnet": { - id: "anthropic/claude-3.7-sonnet", - name: "Anthropic: Claude 3.7 Sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-02-19", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-opus-4.1": { - id: "anthropic/claude-opus-4.1", - name: "Anthropic: Claude Opus 4.1", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "anthropic/claude-3-haiku": { - id: "anthropic/claude-3-haiku", - name: "Anthropic: Claude 3 Haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-03-07", - last_updated: "2024-03-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1.25, cache_read: 0.03, cache_write: 0.3 }, - limit: { context: 200000, output: 4096 }, - }, - "anthropic/claude-sonnet-4.6": { - id: "anthropic/claude-sonnet-4.6", - name: "Anthropic: Claude Sonnet 4.6", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-17", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 1000000, output: 128000 }, - }, - "anthropic/claude-haiku-4.5": { - id: "anthropic/claude-haiku-4.5", - name: "Anthropic: Claude Haiku 4.5", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-3.5-haiku": { - id: "anthropic/claude-3.5-haiku", - name: "Anthropic: Claude 3.5 Haiku", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 }, - limit: { context: 200000, output: 8192 }, - }, - "anthropic/claude-3.7-sonnet:thinking": { - id: "anthropic/claude-3.7-sonnet:thinking", - name: "Anthropic: Claude 3.7 Sonnet (thinking)", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-02-19", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-opus-4.5": { - id: "anthropic/claude-opus-4.5", - name: "Anthropic: Claude Opus 4.5", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-11-24", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-opus-4": { - id: "anthropic/claude-opus-4", - name: "Anthropic: Claude Opus 4", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-05-22", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - }, - "anthropic/claude-sonnet-4": { - id: "anthropic/claude-sonnet-4", - name: "Anthropic: Claude Sonnet 4", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-05-22", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - }, - "anthropic/claude-sonnet-4.5": { - id: "anthropic/claude-sonnet-4.5", - name: "Anthropic: Claude Sonnet 4.5", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-09-29", - last_updated: "2026-03-15", - modalities: { input: ["image", "pdf", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 1000000, output: 64000 }, - }, - "anthropic/claude-opus-4.6": { - id: "anthropic/claude-opus-4.6", - name: "Anthropic: Claude Opus 4.6", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 1000000, output: 128000 }, - }, - "ai21/jamba-large-1.7": { - id: "ai21/jamba-large-1.7", - name: "AI21: Jamba Large 1.7", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-08-09", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8 }, - limit: { context: 256000, output: 4096 }, - }, - "kilo/auto": { - id: "kilo/auto", - name: "Kilo: Auto", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2024-06-01", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25 }, - limit: { context: 1000000, output: 128000 }, - }, - "kilo/auto-free": { - id: "kilo/auto-free", - name: "Deprecated Kilo Auto Free", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-15", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 204800, output: 131072 }, - }, - "kilo/auto-small": { - id: "kilo/auto-small", - name: "Deprecated Kilo Auto Small", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-15", - last_updated: "2026-03-15", - modalities: { input: ["image", "text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4 }, - limit: { context: 400000, output: 128000 }, - }, - "inflection/inflection-3-productivity": { - id: "inflection/inflection-3-productivity", - name: "Inflection: Inflection 3 Productivity", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-10-11", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 8000, output: 1024 }, - }, - "inflection/inflection-3-pi": { - id: "inflection/inflection-3-pi", - name: "Inflection: Inflection 3 Pi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-10-11", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 8000, output: 1024 }, - }, - "nousresearch/hermes-4-405b": { - id: "nousresearch/hermes-4-405b", - name: "Nous: Hermes 4 405B", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-08-25", - last_updated: "2025-08-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 3 }, - limit: { context: 131072, output: 26215 }, - }, - "nousresearch/hermes-3-llama-3.1-70b": { - id: "nousresearch/hermes-3-llama-3.1-70b", - name: "Nous: Hermes 3 70B Instruct", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-08-18", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.3 }, - limit: { context: 131072, output: 32768 }, - }, - "nousresearch/hermes-4-70b": { - id: "nousresearch/hermes-4-70b", - name: "Nous: Hermes 4 70B", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - release_date: "2025-08-25", - last_updated: "2026-03-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.4, cache_read: 0.055 }, - limit: { context: 131072, output: 131072 }, - }, - "nousresearch/hermes-3-llama-3.1-405b": { - id: "nousresearch/hermes-3-llama-3.1-405b", - name: "Nous: Hermes 3 405B Instruct", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-08-16", - last_updated: "2024-08-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 1 }, - limit: { context: 131072, output: 16384 }, - }, - "nousresearch/hermes-2-pro-llama-3-8b": { - id: "nousresearch/hermes-2-pro-llama-3-8b", - name: "NousResearch: Hermes 2 Pro - Llama-3 8B", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - release_date: "2024-05-27", - last_updated: "2024-06-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.14, output: 0.14 }, - limit: { context: 8192, output: 8192 }, - }, - }, - }, - "nano-gpt": { - id: "nano-gpt", - env: ["NANO_GPT_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://nano-gpt.com/api/v1", - name: "NanoGPT", - doc: "https://docs.nano-gpt.com", - models: { - "exa-research-pro": { - id: "exa-research-pro", - name: "Exa (Research Pro)", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-06-04", - last_updated: "2025-06-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 2.5 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "gemini-2.0-pro-exp-02-05": { - id: "gemini-2.0-pro-exp-02-05", - name: "Gemini 2.0 Pro 0205", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-05", - last_updated: "2025-02-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.989, output: 7.956 }, - limit: { context: 2097152, input: 2097152, output: 8192 }, - }, - "qwen-image": { - id: "qwen-image", - name: "Qwen Image", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["image"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "Llama-3.3-70B-Shakudo": { - id: "Llama-3.3-70B-Shakudo", - name: "Llama 3.3 70B Shakudo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "ernie-4.5-8k-preview": { - id: "ernie-4.5-8k-preview", - name: "Ernie 4.5 8k Preview", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-25", - last_updated: "2025-03-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.66, output: 2.6 }, - limit: { context: 8000, input: 8000, output: 16384 }, - }, - "claude-3-7-sonnet-thinking:128000": { - id: "claude-3-7-sonnet-thinking:128000", - name: "Claude 3.7 Sonnet Thinking (128K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-02-24", - last_updated: "2025-02-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 200000, input: 200000, output: 64000 }, - }, - "phi-4-multimodal-instruct": { - id: "phi-4-multimodal-instruct", - name: "Phi 4 Multimodal", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-26", - last_updated: "2025-07-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.11 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "z-image-turbo": { - id: "z-image-turbo", - name: "Z Image Turbo", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-11-27", - last_updated: "2025-11-27", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "Llama-3.3+(3v3.3)-70B-TenyxChat-DaybreakStorywriter": { - id: "Llama-3.3+(3v3.3)-70B-TenyxChat-DaybreakStorywriter", - name: "Llama 3.3+ 70B TenyxChat DaybreakStorywriter", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "mistral-small-31-24b-instruct": { - id: "mistral-small-31-24b-instruct", - name: "Mistral Small 31 24b Instruct", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 128000, input: 128000, output: 131072 }, - }, - "Llama-3.3-70B-The-Omega-Directive-Unslop-v2.0": { - id: "Llama-3.3-70B-The-Omega-Directive-Unslop-v2.0", - name: "Llama 3.3 70B Omega Directive Unslop v2.0", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "Baichuan-M2": { - id: "Baichuan-M2", - name: "Baichuan M2 32B Medical", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-19", - last_updated: "2025-08-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 15.73, output: 15.73 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - "doubao-1.5-vision-pro-32k": { - id: "doubao-1.5-vision-pro-32k", - name: "Doubao 1.5 Vision Pro 32k", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-22", - last_updated: "2025-01-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.459, output: 1.377 }, - limit: { context: 32000, input: 32000, output: 8192 }, - }, - "GLM-4.5-Air-Derestricted-Iceblink-v2-ReExtract": { - id: "GLM-4.5-Air-Derestricted-Iceblink-v2-ReExtract", - name: "GLM 4.5 Air Derestricted Iceblink v2 ReExtract", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-12", - last_updated: "2025-12-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 131072, input: 131072, output: 65536 }, - }, - "claude-opus-4-5-20251101": { - id: "claude-opus-4-5-20251101", - name: "Claude 4.5 Opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-11-01", - last_updated: "2025-11-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 4.998, output: 25.007 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "Llama-3.3-70B-ArliAI-RPMax-v1.4": { - id: "Llama-3.3-70B-ArliAI-RPMax-v1.4", - name: "Llama 3.3 70B RPMax v1.4", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "jamba-large-1.6": { - id: "jamba-large-1.6", - name: "Jamba Large 1.6", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-12", - last_updated: "2025-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.989, output: 7.99 }, - limit: { context: 256000, input: 256000, output: 4096 }, - }, - "Llama-3.3-70B-Aurora-Borealis": { - id: "Llama-3.3-70B-Aurora-Borealis", - name: "Llama 3.3 70B Aurora Borealis", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "ernie-x1-32k": { - id: "ernie-x1-32k", - name: "Ernie X1 32k", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-05-08", - last_updated: "2025-05-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.33, output: 1.32 }, - limit: { context: 32000, input: 32000, output: 16384 }, - }, - "Llama-3.3-70B-Magnum-v4-SE": { - id: "Llama-3.3-70B-Magnum-v4-SE", - name: "Llama 3.3 70B Magnum v4 SE", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "deepseek-reasoner": { - id: "deepseek-reasoner", - name: "DeepSeek Reasoner", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.7 }, - limit: { context: 64000, input: 64000, output: 65536 }, - }, - "KAT-Coder-Pro-V1": { - id: "KAT-Coder-Pro-V1", - name: "KAT Coder Pro V1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-10-28", - last_updated: "2025-10-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 6 }, - limit: { context: 256000, input: 256000, output: 32768 }, - }, - "hunyuan-turbos-20250226": { - id: "hunyuan-turbos-20250226", - name: "Hunyuan Turbo S", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-27", - last_updated: "2025-02-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.187, output: 0.374 }, - limit: { context: 24000, input: 24000, output: 8192 }, - }, - "jamba-large-1.7": { - id: "jamba-large-1.7", - name: "Jamba Large 1.7", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.989, output: 7.99 }, - limit: { context: 256000, input: 256000, output: 4096 }, - }, - "mercury-coder-small": { - id: "mercury-coder-small", - name: "Mercury Coder Small", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-26", - last_updated: "2025-02-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "doubao-1-5-thinking-pro-vision-250415": { - id: "doubao-1-5-thinking-pro-vision-250415", - name: "Doubao 1.5 Thinking Pro Vision", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 2.4 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "yi-medium-200k": { - id: "yi-medium-200k", - name: "Yi Medium 200k", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-03-01", - last_updated: "2024-03-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.499, output: 2.499 }, - limit: { context: 200000, input: 200000, output: 4096 }, - }, - "gemini-2.5-flash-lite-preview-09-2025": { - id: "gemini-2.5-flash-lite-preview-09-2025", - name: "Gemini 2.5 Flash Lite Preview (09/2025)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "deepseek-chat-cheaper": { - id: "deepseek-chat-cheaper", - name: "DeepSeek V3/Chat Cheaper", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.7 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "step-r1-v-mini": { - id: "step-r1-v-mini", - name: "Step R1 V Mini", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-08", - last_updated: "2025-04-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 11 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "gemini-2.5-pro-preview-06-05": { - id: "gemini-2.5-pro-preview-06-05", - name: "Gemini 2.5 Pro Preview 0605", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-06-05", - last_updated: "2025-06-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "yi-lightning": { - id: "yi-lightning", - name: "Yi Lightning", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-10-16", - last_updated: "2024-10-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2006, output: 0.2006 }, - limit: { context: 12000, input: 12000, output: 4096 }, - }, - "deepseek-reasoner-cheaper": { - id: "deepseek-reasoner-cheaper", - name: "Deepseek R1 Cheaper", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.7 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "ernie-4.5-turbo-vl-32k": { - id: "ernie-4.5-turbo-vl-32k", - name: "Ernie 4.5 Turbo VL 32k", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-05-08", - last_updated: "2025-05-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.495, output: 1.43 }, - limit: { context: 32000, input: 32000, output: 16384 }, - }, - "v0-1.0-md": { - id: "v0-1.0-md", - name: "v0 1.0 MD", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-04", - last_updated: "2025-07-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, input: 200000, output: 64000 }, - }, - "Llama-3.3-70B-Ignition-v0.1": { - id: "Llama-3.3-70B-Ignition-v0.1", - name: "Llama 3.3 70B Ignition v0.1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "glm-z1-air": { - id: "glm-z1-air", - name: "GLM Z1 Air", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.07, output: 0.07 }, - limit: { context: 32000, input: 32000, output: 16384 }, - }, - "claude-3-5-sonnet-20241022": { - id: "claude-3-5-sonnet-20241022", - name: "Claude 3.5 Sonnet", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-08-26", - last_updated: "2025-08-26", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 200000, input: 200000, output: 8192 }, - }, - "Llama-3.3-70B-RAWMAW": { - id: "Llama-3.3-70B-RAWMAW", - name: "Llama 3.3 70B RAWMAW", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "Magistral-Small-2506": { - id: "Magistral-Small-2506", - name: "Magistral Small 2506", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.4 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - "ernie-x1-turbo-32k": { - id: "ernie-x1-turbo-32k", - name: "Ernie X1 Turbo 32k", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-05-08", - last_updated: "2025-05-08", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.165, output: 0.66 }, - limit: { context: 32000, input: 32000, output: 16384 }, - }, - "sonar-reasoning-pro": { - id: "sonar-reasoning-pro", - name: "Perplexity Reasoning Pro", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.006, output: 7.9985 }, - limit: { context: 127000, input: 127000, output: 128000 }, - }, - "deepseek-r1-sambanova": { - id: "deepseek-r1-sambanova", - name: "DeepSeek R1 Fast", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-20", - last_updated: "2025-02-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 4.998, output: 6.987 }, - limit: { context: 128000, input: 128000, output: 4096 }, - }, - "claude-3-7-sonnet-thinking:1024": { - id: "claude-3-7-sonnet-thinking:1024", - name: "Claude 3.7 Sonnet Thinking (1K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-02-24", - last_updated: "2025-02-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 200000, input: 200000, output: 64000 }, - }, - "Llama-3.3-70B-Magnum-v4-SE-Cirrus-x1-SLERP": { - id: "Llama-3.3-70B-Magnum-v4-SE-Cirrus-x1-SLERP", - name: "Llama 3.3 70B Magnum v4 SE Cirrus x1 SLERP", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-26", - last_updated: "2025-07-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "Llama-3.3-70B-ArliAI-RPMax-v3": { - id: "Llama-3.3-70B-ArliAI-RPMax-v3", - name: "Llama 3.3 70B ArliAI RPMax v3", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "qwen-long": { - id: "qwen-long", - name: "Qwen Long 10M", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-25", - last_updated: "2025-01-25", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1003, output: 0.408 }, - limit: { context: 10000000, input: 10000000, output: 8192 }, - }, - "gemini-2.5-flash-preview-04-17": { - id: "gemini-2.5-flash-preview-04-17", - name: "Gemini 2.5 Flash Preview", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-04-17", - last_updated: "2025-04-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "Llama-3.3-70B-Progenitor-V3.3": { - id: "Llama-3.3-70B-Progenitor-V3.3", - name: "Llama 3.3 70B Progenitor V3.3", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-26", - last_updated: "2025-07-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "GLM-4.5-Air-Derestricted-Iceblink-v2": { - id: "GLM-4.5-Air-Derestricted-Iceblink-v2", - name: "GLM 4.5 Air Derestricted Iceblink v2", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 158600, input: 158600, output: 65536 }, - }, - "gemini-2.5-flash-preview-09-2025": { - id: "gemini-2.5-flash-preview-09-2025", - name: "Gemini 2.5 Flash Preview (09/2025)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "study_gpt-chatgpt-4o-latest": { - id: "study_gpt-chatgpt-4o-latest", - name: "Study Mode", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 4.998, output: 14.994 }, - limit: { context: 200000, input: 200000, output: 16384 }, - }, - "qwq-32b": { - id: "qwq-32b", - name: "Qwen: QwQ 32B", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25599999, output: 0.30499999 }, - limit: { context: 128000, input: 128000, output: 32768 }, - }, - "gemini-2.5-pro-preview-05-06": { - id: "gemini-2.5-pro-preview-05-06", - name: "Gemini 2.5 Pro Preview 0506", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-05-06", - last_updated: "2025-05-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "Llama-3.3-70B-MS-Nevoria": { - id: "Llama-3.3-70B-MS-Nevoria", - name: "Llama 3.3 70B MS Nevoria", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "doubao-seed-1-6-250615": { - id: "doubao-seed-1-6-250615", - name: "Doubao Seed 1.6", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-06-15", - last_updated: "2025-06-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.204, output: 0.51 }, - limit: { context: 256000, input: 256000, output: 16384 }, - }, - "gemini-2.5-flash-preview-05-20": { - id: "gemini-2.5-flash-preview-05-20", - name: "Gemini 2.5 Flash 0520", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-05-20", - last_updated: "2025-05-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 1048000, input: 1048000, output: 65536 }, - }, - "glm-4": { - id: "glm-4", - name: "GLM-4", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-01-16", - last_updated: "2024-01-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 14.994 }, - limit: { context: 128000, input: 128000, output: 4096 }, - }, - "azure-gpt-4-turbo": { - id: "azure-gpt-4-turbo", - name: "Azure gpt-4-turbo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2023-11-06", - last_updated: "2024-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 9.996, output: 30.005 }, - limit: { context: 128000, input: 128000, output: 4096 }, - }, - "Llama-3.3-70B-Legion-V2.1": { - id: "Llama-3.3-70B-Legion-V2.1", - name: "Llama 3.3 70B Legion V2.1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "claude-3-7-sonnet-thinking:32768": { - id: "claude-3-7-sonnet-thinking:32768", - name: "Claude 3.7 Sonnet Thinking (32K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-07-15", - last_updated: "2025-07-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 200000, input: 200000, output: 64000 }, - }, - "gemini-2.5-flash": { - id: "gemini-2.5-flash", - name: "Gemini 2.5 Flash", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-06-05", - last_updated: "2025-06-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "asi1-mini": { - id: "asi1-mini", - name: "ASI1 Mini", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-25", - last_updated: "2025-03-25", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 1 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "gemini-exp-1206": { - id: "gemini-exp-1206", - name: "Gemini 2.0 Pro 1206", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.258, output: 4.998 }, - limit: { context: 2097152, input: 2097152, output: 8192 }, - }, - "qwen-max": { - id: "qwen-max", - name: "Qwen 2.5 Max", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-04-03", - last_updated: "2024-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5997, output: 6.392 }, - limit: { context: 32000, input: 32000, output: 8192 }, - }, - brave: { - id: "brave", - name: "Brave (Answers)", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2023-03-02", - last_updated: "2024-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 5 }, - limit: { context: 8192, input: 8192, output: 8192 }, - }, - "doubao-1-5-thinking-pro-250415": { - id: "doubao-1-5-thinking-pro-250415", - name: "Doubao 1.5 Thinking Pro", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-17", - last_updated: "2025-04-17", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 2.4 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "claude-sonnet-4-thinking:64000": { - id: "claude-sonnet-4-thinking:64000", - name: "Claude 4 Sonnet Thinking (64K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 1000000, input: 1000000, output: 64000 }, - }, - "GLM-4.5-Air-Derestricted-Steam-ReExtract": { - id: "GLM-4.5-Air-Derestricted-Steam-ReExtract", - name: "GLM 4.5 Air Derestricted Steam ReExtract", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-12", - last_updated: "2025-12-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 131072, input: 131072, output: 65536 }, - }, - "kimi-k2-instruct-fast": { - id: "kimi-k2-instruct-fast", - name: "Kimi K2 0711 Fast", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-15", - last_updated: "2025-07-15", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 2 }, - limit: { context: 131072, input: 131072, output: 16384 }, - }, - "Llama-3.3-70B-GeneticLemonade-Opus": { - id: "Llama-3.3-70B-GeneticLemonade-Opus", - name: "Llama 3.3 70B GeneticLemonade Opus", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "Gemma-3-27B-Big-Tiger-v3": { - id: "Gemma-3-27B-Big-Tiger-v3", - name: "Gemma 3 27B Big Tiger v3", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-08", - last_updated: "2025-08-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "doubao-seed-2-0-mini-260215": { - id: "doubao-seed-2-0-mini-260215", - name: "Doubao Seed 2.0 Mini", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.0493, output: 0.4845 }, - limit: { context: 256000, input: 256000, output: 32000 }, - }, - "claude-sonnet-4-5-20250929-thinking": { - id: "claude-sonnet-4-5-20250929-thinking", - name: "Claude Sonnet 4.5 Thinking", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 1000000, input: 1000000, output: 64000 }, - }, - "glm-4-air": { - id: "glm-4-air", - name: "GLM-4 Air", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-06-05", - last_updated: "2024-06-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2006, output: 0.2006 }, - limit: { context: 128000, input: 128000, output: 4096 }, - }, - "GLM-4.5-Air-Derestricted-Iceblink-ReExtract": { - id: "GLM-4.5-Air-Derestricted-Iceblink-ReExtract", - name: "GLM 4.5 Air Derestricted Iceblink ReExtract", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-12", - last_updated: "2025-12-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 131072, input: 131072, output: 98304 }, - }, - "gemini-2.0-pro-reasoner": { - id: "gemini-2.0-pro-reasoner", - name: "Gemini 2.0 Pro Reasoner", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-05", - last_updated: "2025-02-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.292, output: 4.998 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "gemini-2.0-flash-001": { - id: "gemini-2.0-flash-001", - name: "Gemini 2.0 Flash", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1003, output: 0.408 }, - limit: { context: 1000000, input: 1000000, output: 8192 }, - }, - "glm-4-plus": { - id: "glm-4-plus", - name: "GLM-4 Plus", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-08-01", - last_updated: "2024-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 7.497, output: 7.497 }, - limit: { context: 128000, input: 128000, output: 4096 }, - }, - "gemini-2.0-flash-exp-image-generation": { - id: "gemini-2.0-flash-exp-image-generation", - name: "Gemini Text + Image", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 32767, input: 32767, output: 8192 }, - }, - "GLM-4.5-Air-Derestricted": { - id: "GLM-4.5-Air-Derestricted", - name: "GLM 4.5 Air Derestricted", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 202600, input: 202600, output: 98304 }, - }, - "gemini-2.0-flash-thinking-exp-1219": { - id: "gemini-2.0-flash-thinking-exp-1219", - name: "Gemini 2.0 Flash Thinking 1219", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-19", - last_updated: "2024-12-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1003, output: 0.408 }, - limit: { context: 32767, input: 32767, output: 8192 }, - }, - "glm-4.1v-thinking-flashx": { - id: "glm-4.1v-thinking-flashx", - name: "GLM 4.1V Thinking FlashX", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.3 }, - limit: { context: 64000, input: 64000, output: 8192 }, - }, - "Llama-3.3-70B-StrawberryLemonade-v1.0": { - id: "Llama-3.3-70B-StrawberryLemonade-v1.0", - name: "Llama 3.3 70B StrawberryLemonade v1.0", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "Llama-3.3-70B-Fallen-v1": { - id: "Llama-3.3-70B-Fallen-v1", - name: "Llama 3.3 70B Fallen v1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "Gemma-3-27B-Nidum-Uncensored": { - id: "Gemma-3-27B-Nidum-Uncensored", - name: "Gemma 3 27B Nidum Uncensored", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-08", - last_updated: "2025-08-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 96000 }, - }, - "Llama-3.3-70B-Electranova-v1.0": { - id: "Llama-3.3-70B-Electranova-v1.0", - name: "Llama 3.3 70B Electranova v1.0", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "grok-3-fast-beta": { - id: "grok-3-fast-beta", - name: "Grok 3 Fast Beta", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25 }, - limit: { context: 131072, input: 131072, output: 131072 }, - }, - "qwen-turbo": { - id: "qwen-turbo", - name: "Qwen Turbo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-11-01", - last_updated: "2024-11-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.04998, output: 0.2006 }, - limit: { context: 1000000, input: 1000000, output: 8192 }, - }, - "Llama-3.3-70B-Sapphira-0.1": { - id: "Llama-3.3-70B-Sapphira-0.1", - name: "Llama 3.3 70B Sapphira 0.1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "gemini-2.5-pro-preview-03-25": { - id: "gemini-2.5-pro-preview-03-25", - name: "Gemini 2.5 Pro Preview 0325", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-03-25", - last_updated: "2025-03-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "step-2-16k-exp": { - id: "step-2-16k-exp", - name: "Step-2 16k Exp", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-05", - last_updated: "2024-07-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 7.004, output: 19.992 }, - limit: { context: 16000, input: 16000, output: 8192 }, - }, - chroma: { - id: "chroma", - name: "Chroma", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-08-12", - last_updated: "2025-08-12", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - sonar: { - id: "sonar", - name: "Perplexity Simple", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.003, output: 1.003 }, - limit: { context: 127000, input: 127000, output: 128000 }, - }, - fastgpt: { - id: "fastgpt", - name: "Web Answer", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2023-08-01", - last_updated: "2024-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 7.5, output: 7.5 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - "claude-sonnet-4-thinking:8192": { - id: "claude-sonnet-4-thinking:8192", - name: "Claude 4 Sonnet Thinking (8K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 1000000, input: 1000000, output: 64000 }, - }, - "Llama-3.3-70B-Electra-R1": { - id: "Llama-3.3-70B-Electra-R1", - name: "Llama 3.3 70B Electra R1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "Llama-3.3-70B-Fallen-R1-v1": { - id: "Llama-3.3-70B-Fallen-R1-v1", - name: "Llama 3.3 70B Fallen R1 v1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "Gemma-3-27B-it-Abliterated": { - id: "Gemma-3-27B-it-Abliterated", - name: "Gemma 3 27B IT Abliterated", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-03", - last_updated: "2025-07-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.42, output: 0.42 }, - limit: { context: 32768, input: 32768, output: 96000 }, - }, - "doubao-1.5-pro-256k": { - id: "doubao-1.5-pro-256k", - name: "Doubao 1.5 Pro 256k", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-12", - last_updated: "2025-03-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.799, output: 1.445 }, - limit: { context: 256000, input: 256000, output: 16384 }, - }, - "claude-opus-4-thinking": { - id: "claude-opus-4-thinking", - name: "Claude 4 Opus Thinking", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-07-15", - last_updated: "2025-07-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 75.004 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "deepseek-r1": { - id: "deepseek-r1", - name: "DeepSeek R1", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.7 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "doubao-1-5-thinking-vision-pro-250428": { - id: "doubao-1-5-thinking-vision-pro-250428", - name: "Doubao 1.5 Thinking Vision Pro", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-05-15", - last_updated: "2025-05-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.55, output: 1.43 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "doubao-seed-2-0-lite-260215": { - id: "doubao-seed-2-0-lite-260215", - name: "Doubao Seed 2.0 Lite", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1462, output: 0.8738 }, - limit: { context: 256000, input: 256000, output: 32000 }, - }, - "claude-opus-4-20250514": { - id: "claude-opus-4-20250514", - name: "Claude 4 Opus", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-05-14", - last_updated: "2025-05-14", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 75.004 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "qwen25-vl-72b-instruct": { - id: "qwen25-vl-72b-instruct", - name: "Qwen25 VL 72b", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-05-10", - last_updated: "2025-05-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.69989, output: 0.69989 }, - limit: { context: 32000, input: 32000, output: 32768 }, - }, - "azure-gpt-4o": { - id: "azure-gpt-4o", - name: "Azure gpt-4o", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.499, output: 9.996 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "sonar-deep-research": { - id: "sonar-deep-research", - name: "Perplexity Deep Research", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-25", - last_updated: "2025-02-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3.4, output: 13.6 }, - limit: { context: 60000, input: 60000, output: 128000 }, - }, - "ernie-4.5-turbo-128k": { - id: "ernie-4.5-turbo-128k", - name: "Ernie 4.5 Turbo 128k", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-05-08", - last_updated: "2025-05-08", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.132, output: 0.55 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "azure-o1": { - id: "azure-o1", - name: "Azure o1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-17", - last_updated: "2024-12-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 59.993 }, - limit: { context: 200000, input: 200000, output: 100000 }, - }, - "gemini-3-pro-preview-thinking": { - id: "gemini-3-pro-preview-thinking", - name: "Gemini 3 Pro Thinking", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "grok-3-mini-beta": { - id: "grok-3-mini-beta", - name: "Grok 3 Mini Beta", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.5 }, - limit: { context: 131072, input: 131072, output: 131072 }, - }, - "claude-opus-4-1-thinking": { - id: "claude-opus-4-1-thinking", - name: "Claude 4.1 Opus Thinking", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 75.004 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "gemini-2.5-flash-nothinking": { - id: "gemini-2.5-flash-nothinking", - name: "Gemini 2.5 Flash (No Thinking)", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-06-05", - last_updated: "2025-06-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "doubao-seed-1-8-251215": { - id: "doubao-seed-1-8-251215", - name: "Doubao Seed 1.8", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-15", - last_updated: "2025-12-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.612, output: 6.12 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "claude-3-7-sonnet-thinking:8192": { - id: "claude-3-7-sonnet-thinking:8192", - name: "Claude 3.7 Sonnet Thinking (8K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-02-24", - last_updated: "2025-02-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 200000, input: 200000, output: 64000 }, - }, - "qvq-max": { - id: "qvq-max", - name: "Qwen: QvQ Max", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-28", - last_updated: "2025-03-28", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.4, output: 5.3 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "claude-sonnet-4-5-20250929": { - id: "claude-sonnet-4-5-20250929", - name: "Claude Sonnet 4.5", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 1000000, input: 1000000, output: 64000 }, - }, - "auto-model-basic": { - id: "auto-model-basic", - name: "Auto model (Basic)", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-06-01", - last_updated: "2024-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 9.996, output: 19.992 }, - limit: { context: 1000000, input: 1000000, output: 1000000 }, - }, - "Llama-3.3-70B-The-Omega-Directive-Unslop-v2.1": { - id: "Llama-3.3-70B-The-Omega-Directive-Unslop-v2.1", - name: "Llama 3.3 70B Omega Directive Unslop v2.1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "claude-3-5-haiku-20241022": { - id: "claude-3-5-haiku-20241022", - name: "Claude 3.5 Haiku", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8, output: 4 }, - limit: { context: 200000, input: 200000, output: 8192 }, - }, - "glm-4-plus-0111": { - id: "glm-4-plus-0111", - name: "GLM 4 Plus 0111", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 9.996, output: 9.996 }, - limit: { context: 128000, input: 128000, output: 4096 }, - }, - "Llama-3.3-70B-Bigger-Body": { - id: "Llama-3.3-70B-Bigger-Body", - name: "Llama 3.3 70B Bigger Body", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "gemini-2.5-flash-lite": { - id: "gemini-2.5-flash-lite", - name: "Gemini 2.5 Flash Lite", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "KAT-Coder-Air-V1": { - id: "KAT-Coder-Air-V1", - name: "KAT Coder Air V1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-10-28", - last_updated: "2025-10-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.2 }, - limit: { context: 128000, input: 128000, output: 32768 }, - }, - "MiniMax-M2": { - id: "MiniMax-M2", - name: "MiniMax M2", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-10-25", - last_updated: "2025-10-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.17, output: 1.53 }, - limit: { context: 200000, input: 200000, output: 131072 }, - }, - "doubao-seed-1-6-flash-250615": { - id: "doubao-seed-1-6-flash-250615", - name: "Doubao Seed 1.6 Flash", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-06-15", - last_updated: "2025-06-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.0374, output: 0.374 }, - limit: { context: 256000, input: 256000, output: 16384 }, - }, - "glm-4-air-0111": { - id: "glm-4-air-0111", - name: "GLM 4 Air 0111", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-11", - last_updated: "2025-01-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1394, output: 0.1394 }, - limit: { context: 128000, input: 128000, output: 4096 }, - }, - "phi-4-mini-instruct": { - id: "phi-4-mini-instruct", - name: "Phi 4 Mini", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-26", - last_updated: "2025-07-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.17, output: 0.68 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "jamba-mini-1.6": { - id: "jamba-mini-1.6", - name: "Jamba Mini 1.6", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-01", - last_updated: "2025-03-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1989, output: 0.408 }, - limit: { context: 256000, input: 256000, output: 4096 }, - }, - "v0-1.5-md": { - id: "v0-1.5-md", - name: "v0 1.5 MD", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-04", - last_updated: "2025-07-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 200000, input: 200000, output: 64000 }, - }, - "command-a-reasoning-08-2025": { - id: "command-a-reasoning-08-2025", - name: "Cohere Command A (08/2025)", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-22", - last_updated: "2025-08-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 256000, input: 256000, output: 8192 }, - }, - "kimi-thinking-preview": { - id: "kimi-thinking-preview", - name: "Kimi Thinking Preview", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-05-07", - last_updated: "2025-05-07", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 31.46, output: 31.46 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "claude-3-5-sonnet-20240620": { - id: "claude-3-5-sonnet-20240620", - name: "Claude 3.5 Sonnet Old", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2024-06-20", - last_updated: "2024-06-20", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 200000, input: 200000, output: 8192 }, - }, - "deepseek-v3-0324": { - id: "deepseek-v3-0324", - name: "DeepSeek Chat 0324", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-03-24", - last_updated: "2025-03-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.7 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "claude-sonnet-4-thinking:1024": { - id: "claude-sonnet-4-thinking:1024", - name: "Claude 4 Sonnet Thinking (1K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 1000000, input: 1000000, output: 64000 }, - }, - "Llama-3.3-70B-Incandescent-Malevolence": { - id: "Llama-3.3-70B-Incandescent-Malevolence", - name: "Llama 3.3 70B Incandescent Malevolence", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "doubao-1.5-pro-32k": { - id: "doubao-1.5-pro-32k", - name: "Doubao 1.5 Pro 32k", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-22", - last_updated: "2025-01-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1343, output: 0.3349 }, - limit: { context: 32000, input: 32000, output: 8192 }, - }, - "Llama-3.3-70B-Forgotten-Safeword-3.6": { - id: "Llama-3.3-70B-Forgotten-Safeword-3.6", - name: "Llama 3.3 70B Forgotten Safeword 3.6", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "step-2-mini": { - id: "step-2-mini", - name: "Step-2 Mini", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-05", - last_updated: "2024-07-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2006, output: 0.408 }, - limit: { context: 8000, input: 8000, output: 4096 }, - }, - "Mistral-Nemo-12B-Instruct-2407": { - id: "Mistral-Nemo-12B-Instruct-2407", - name: "Mistral Nemo 12B Instruct 2407", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.01, output: 0.01 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "Baichuan4-Turbo": { - id: "Baichuan4-Turbo", - name: "Baichuan 4 Turbo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-19", - last_updated: "2025-08-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.42, output: 2.42 }, - limit: { context: 128000, input: 128000, output: 32768 }, - }, - "ernie-5.0-thinking-latest": { - id: "ernie-5.0-thinking-latest", - name: "Ernie 5.0 Thinking", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 2 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "qwen3-30b-a3b-instruct-2507": { - id: "qwen3-30b-a3b-instruct-2507", - name: "Qwen3 30B A3B Instruct 2507", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-20", - last_updated: "2025-02-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5 }, - limit: { context: 256000, input: 256000, output: 32768 }, - }, - "Gemma-3-27B-Glitter": { - id: "Gemma-3-27B-Glitter", - name: "Gemma 3 27B Glitter", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-10", - last_updated: "2025-03-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "claude-opus-4-thinking:32000": { - id: "claude-opus-4-thinking:32000", - name: "Claude 4 Opus Thinking (32K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 75.004 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "auto-model-premium": { - id: "auto-model-premium", - name: "Auto model (Premium)", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-06-01", - last_updated: "2024-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 9.996, output: 19.992 }, - limit: { context: 1000000, input: 1000000, output: 1000000 }, - }, - "claude-3-7-sonnet-20250219": { - id: "claude-3-7-sonnet-20250219", - name: "Claude 3.7 Sonnet", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 200000, input: 200000, output: 16000 }, - }, - "gemini-2.0-flash-thinking-exp-01-21": { - id: "gemini-2.0-flash-thinking-exp-01-21", - name: "Gemini 2.0 Flash Thinking 0121", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-01-21", - last_updated: "2025-01-21", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 1.003 }, - limit: { context: 1000000, input: 1000000, output: 8192 }, - }, - "claude-sonnet-4-thinking:32768": { - id: "claude-sonnet-4-thinking:32768", - name: "Claude 4 Sonnet Thinking (32K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 1000000, input: 1000000, output: 64000 }, - }, - "claude-opus-4-1-thinking:32768": { - id: "claude-opus-4-1-thinking:32768", - name: "Claude 4.1 Opus Thinking (32K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 75.004 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "jamba-large": { - id: "jamba-large", - name: "Jamba Large", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.989, output: 7.99 }, - limit: { context: 256000, input: 256000, output: 4096 }, - }, - "qwen3-coder-30b-a3b-instruct": { - id: "qwen3-coder-30b-a3b-instruct", - name: "Qwen3 Coder 30B A3B Instruct", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "Llama-3.3-70B-MiraiFanfare": { - id: "Llama-3.3-70B-MiraiFanfare", - name: "Llama 3.3 70b Mirai Fanfare", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-26", - last_updated: "2025-07-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.493, output: 0.493 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "venice-uncensored:web": { - id: "venice-uncensored:web", - name: "Venice Uncensored Web", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-05-01", - last_updated: "2024-05-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 0.4 }, - limit: { context: 80000, input: 80000, output: 16384 }, - }, - "qwen3-max-2026-01-23": { - id: "qwen3-max-2026-01-23", - name: "Qwen3 Max 2026-01-23", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-01-26", - last_updated: "2026-01-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.2002, output: 6.001 }, - limit: { context: 256000, input: 256000, output: 32768 }, - }, - "gemini-2.5-flash-lite-preview-09-2025-thinking": { - id: "gemini-2.5-flash-lite-preview-09-2025-thinking", - name: "Gemini 2.5 Flash Lite Preview (09/2025) – Thinking", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "ernie-x1-32k-preview": { - id: "ernie-x1-32k-preview", - name: "Ernie X1 32k", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-03", - last_updated: "2025-04-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.33, output: 1.32 }, - limit: { context: 32000, input: 32000, output: 16384 }, - }, - "glm-z1-airx": { - id: "glm-z1-airx", - name: "GLM Z1 AirX", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7, output: 0.7 }, - limit: { context: 32000, input: 32000, output: 16384 }, - }, - "ernie-x1.1-preview": { - id: "ernie-x1.1-preview", - name: "ERNIE X1.1", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-10", - last_updated: "2025-09-10", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 64000, input: 64000, output: 8192 }, - }, - "claude-haiku-4-5-20251001": { - id: "claude-haiku-4-5-20251001", - name: "Claude Haiku 4.5", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-10-15", - last_updated: "2025-10-15", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5 }, - limit: { context: 200000, input: 200000, output: 64000 }, - }, - "exa-research": { - id: "exa-research", - name: "Exa (Research)", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-06-04", - last_updated: "2025-06-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 2.5 }, - limit: { context: 8192, input: 8192, output: 8192 }, - }, - "Llama-3.3-70B-Mokume-Gane-R1": { - id: "Llama-3.3-70B-Mokume-Gane-R1", - name: "Llama 3.3 70B Mokume Gane R1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "glm-4.1v-thinking-flash": { - id: "glm-4.1v-thinking-flash", - name: "GLM 4.1V Thinking Flash", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.3 }, - limit: { context: 64000, input: 64000, output: 8192 }, - }, - "Llama-3.3-70B-GeneticLemonade-Unleashed-v3": { - id: "Llama-3.3-70B-GeneticLemonade-Unleashed-v3", - name: "Llama 3.3 70B GeneticLemonade Unleashed v3", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "Llama-3.3-70B-Predatorial-Extasy": { - id: "Llama-3.3-70B-Predatorial-Extasy", - name: "Llama 3.3 70B Predatorial Extasy", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "deepseek-chat": { - id: "deepseek-chat", - name: "DeepSeek V3/Deepseek Chat", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-02-27", - last_updated: "2025-02-27", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.7 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "glm-4-airx": { - id: "glm-4-airx", - name: "GLM-4 AirX", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-06-05", - last_updated: "2024-06-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.006, output: 2.006 }, - limit: { context: 8000, input: 8000, output: 4096 }, - }, - "gemini-2.5-flash-lite-preview-06-17": { - id: "gemini-2.5-flash-lite-preview-06-17", - name: "Gemini 2.5 Flash Lite Preview", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-06-17", - last_updated: "2025-06-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "doubao-seed-1-6-thinking-250615": { - id: "doubao-seed-1-6-thinking-250615", - name: "Doubao Seed 1.6 Thinking", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-06-15", - last_updated: "2025-06-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.204, output: 2.04 }, - limit: { context: 256000, input: 256000, output: 16384 }, - }, - "claude-3-7-sonnet-thinking": { - id: "claude-3-7-sonnet-thinking", - name: "Claude 3.7 Sonnet Thinking", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-02-24", - last_updated: "2025-02-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 200000, input: 200000, output: 16000 }, - }, - "GLM-4.5-Air-Derestricted-Steam": { - id: "GLM-4.5-Air-Derestricted-Steam", - name: "GLM 4.5 Air Derestricted Steam", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 220600, input: 220600, output: 65536 }, - }, - "gemini-3-pro-image-preview": { - id: "gemini-3-pro-image-preview", - name: "Gemini 3 Pro Image", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "MiniMax-M1": { - id: "MiniMax-M1", - name: "MiniMax M1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-06-16", - last_updated: "2025-06-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1394, output: 1.3328 }, - limit: { context: 1000000, input: 1000000, output: 131072 }, - }, - "ernie-5.0-thinking-preview": { - id: "ernie-5.0-thinking-preview", - name: "Ernie 5.0 Thinking Preview", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 2 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "claude-opus-4-thinking:1024": { - id: "claude-opus-4-thinking:1024", - name: "Claude 4 Opus Thinking (1K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 75.004 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "Llama-3.3-70B-Strawberrylemonade-v1.2": { - id: "Llama-3.3-70B-Strawberrylemonade-v1.2", - name: "Llama 3.3 70B StrawberryLemonade v1.2", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "Llama-3.3-70B-Vulpecula-R1": { - id: "Llama-3.3-70B-Vulpecula-R1", - name: "Llama 3.3 70B Vulpecula R1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "GLM-4.6-Derestricted-v5": { - id: "GLM-4.6-Derestricted-v5", - name: "GLM 4.6 Derestricted v5", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.5 }, - limit: { context: 131072, input: 131072, output: 8192 }, - }, - "Llama-3.3-70B-Cirrus-x1": { - id: "Llama-3.3-70B-Cirrus-x1", - name: "Llama 3.3 70B Cirrus x1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "Llama-3.3-70B-ArliAI-RPMax-v2": { - id: "Llama-3.3-70B-ArliAI-RPMax-v2", - name: "Llama 3.3 70B ArliAI RPMax v2", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-08", - last_updated: "2025-08-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "doubao-seed-code-preview-latest": { - id: "doubao-seed-code-preview-latest", - name: "Doubao Seed Code Preview", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 256000, input: 256000, output: 16384 }, - }, - "sonar-pro": { - id: "sonar-pro", - name: "Perplexity Pro", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-19", - last_updated: "2025-02-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 200000, input: 200000, output: 128000 }, - }, - "Llama-3.3+(3.1v3.3)-70B-New-Dawn-v1.1": { - id: "Llama-3.3+(3.1v3.3)-70B-New-Dawn-v1.1", - name: "Llama 3.3+ 70B New Dawn v1.1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "qwen3-vl-235b-a22b-thinking": { - id: "qwen3-vl-235b-a22b-thinking", - name: "Qwen3 VL 235B A22B Thinking", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-08-26", - last_updated: "2025-08-26", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 6 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - "claude-sonnet-4-thinking": { - id: "claude-sonnet-4-thinking", - name: "Claude 4 Sonnet Thinking", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-02-24", - last_updated: "2025-02-24", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 1000000, input: 1000000, output: 64000 }, - }, - "Qwen2.5-32B-EVA-v0.2": { - id: "Qwen2.5-32B-EVA-v0.2", - name: "Qwen 2.5 32b EVA", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-09-01", - last_updated: "2024-09-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.493, output: 0.493 }, - limit: { context: 24576, input: 24576, output: 8192 }, - }, - "v0-1.5-lg": { - id: "v0-1.5-lg", - name: "v0 1.5 LG", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-04", - last_updated: "2025-07-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75 }, - limit: { context: 1000000, input: 1000000, output: 64000 }, - }, - "Llama-3.3-70B-Cu-Mai-R1": { - id: "Llama-3.3-70B-Cu-Mai-R1", - name: "Llama 3.3 70B Cu Mai R1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - hidream: { - id: "hidream", - name: "Hidream", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2024-01-01", - last_updated: "2024-01-01", - modalities: { input: ["text"], output: ["image"] }, - open_weights: false, - limit: { context: 0, output: 0 }, - }, - "auto-model": { - id: "auto-model", - name: "Auto model", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-06-01", - last_updated: "2024-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 1000000, input: 1000000, output: 1000000 }, - }, - "jamba-mini-1.7": { - id: "jamba-mini-1.7", - name: "Jamba Mini 1.7", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1989, output: 0.408 }, - limit: { context: 256000, input: 256000, output: 4096 }, - }, - "doubao-seed-2-0-pro-260215": { - id: "doubao-seed-2-0-pro-260215", - name: "Doubao Seed 2.0 Pro", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.782, output: 3.876 }, - limit: { context: 256000, input: 256000, output: 128000 }, - }, - "Llama-3.3-70B-Nova": { - id: "Llama-3.3-70B-Nova", - name: "Llama 3.3 70B Nova", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "gemini-2.5-flash-preview-09-2025-thinking": { - id: "gemini-2.5-flash-preview-09-2025-thinking", - name: "Gemini 2.5 Flash Preview (09/2025) – Thinking", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 2.5 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "Llama-3.3-70B-Sapphira-0.2": { - id: "Llama-3.3-70B-Sapphira-0.2", - name: "Llama 3.3 70B Sapphira 0.2", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "auto-model-standard": { - id: "auto-model-standard", - name: "Auto model (Standard)", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-06-01", - last_updated: "2024-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 9.996, output: 19.992 }, - limit: { context: 1000000, input: 1000000, output: 1000000 }, - }, - "grok-3-mini-fast-beta": { - id: "grok-3-mini-fast-beta", - name: "Grok 3 Mini Fast Beta", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 4 }, - limit: { context: 131072, input: 131072, output: 131072 }, - }, - "qwen-plus": { - id: "qwen-plus", - name: "Qwen Plus", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2024-01-25", - last_updated: "2024-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3995, output: 1.2002 }, - limit: { context: 995904, input: 995904, output: 32768 }, - }, - "Meta-Llama-3-1-8B-Instruct-FP8": { - id: "Meta-Llama-3-1-8B-Instruct-FP8", - name: "Llama 3.1 8B (decentralized)", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0.03 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "step-3": { - id: "step-3", - name: "Step-3", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-31", - last_updated: "2025-07-31", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2499, output: 0.6494 }, - limit: { context: 65536, input: 65536, output: 8192 }, - }, - "Gemma-3-27B-it": { - id: "Gemma-3-27B-it", - name: "Gemma 3 27B IT", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-10", - last_updated: "2025-03-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "universal-summarizer": { - id: "universal-summarizer", - name: "Universal Summarizer", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2023-05-01", - last_updated: "2024-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 30, output: 30 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - deepclaude: { - id: "deepclaude", - name: "DeepClaude", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-01", - last_updated: "2025-02-01", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "brave-pro": { - id: "brave-pro", - name: "Brave (Pro)", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2023-03-02", - last_updated: "2024-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 5 }, - limit: { context: 8192, input: 8192, output: 8192 }, - }, - "gemini-3-pro-preview": { - id: "gemini-3-pro-preview", - name: "Gemini 3 Pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 12 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "claude-3-7-sonnet-reasoner": { - id: "claude-3-7-sonnet-reasoner", - name: "Claude 3.7 Sonnet Reasoner", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-29", - last_updated: "2025-03-29", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "gemini-2.0-flash-lite": { - id: "gemini-2.0-flash-lite", - name: "Gemini 2.0 Flash Lite", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.0748, output: 0.306 }, - limit: { context: 1000000, input: 1000000, output: 8192 }, - }, - "claude-opus-4-thinking:8192": { - id: "claude-opus-4-thinking:8192", - name: "Claude 4 Opus Thinking (8K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 75.004 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "claude-opus-4-thinking:32768": { - id: "claude-opus-4-thinking:32768", - name: "Claude 4 Opus Thinking (32K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 75.004 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "glm-zero-preview": { - id: "glm-zero-preview", - name: "GLM Zero Preview", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.802, output: 1.802 }, - limit: { context: 8000, input: 8000, output: 4096 }, - }, - "azure-gpt-4o-mini": { - id: "azure-gpt-4o-mini", - name: "Azure gpt-4o-mini", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1496, output: 0.595 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "deepseek-math-v2": { - id: "deepseek-math-v2", - name: "DeepSeek Math V2", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-03", - last_updated: "2025-12-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 2.2 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "glm-4-long": { - id: "glm-4-long", - name: "GLM-4 Long", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-08-01", - last_updated: "2024-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2006, output: 0.2006 }, - limit: { context: 1000000, input: 1000000, output: 4096 }, - }, - "GLM-4.5-Air-Derestricted-Iceblink": { - id: "GLM-4.5-Air-Derestricted-Iceblink", - name: "GLM 4.5 Air Derestricted Iceblink", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 131072, input: 131072, output: 98304 }, - }, - "claude-opus-4-1-thinking:1024": { - id: "claude-opus-4-1-thinking:1024", - name: "Claude 4.1 Opus Thinking (1K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 75.004 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "qwen3-vl-235b-a22b-instruct-original": { - id: "qwen3-vl-235b-a22b-instruct-original", - name: "Qwen3 VL 235B A22B Instruct Original", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.2 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - "Llama-3.3+(3.1v3.3)-70B-Hanami-x1": { - id: "Llama-3.3+(3.1v3.3)-70B-Hanami-x1", - name: "Llama 3.3+ 70B Hanami x1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "claude-opus-4-1-thinking:8192": { - id: "claude-opus-4-1-thinking:8192", - name: "Claude 4.1 Opus Thinking (8K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 75.004 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "Llama-3.3-70B-Damascus-R1": { - id: "Llama-3.3-70B-Damascus-R1", - name: "Damascus R1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "Gemma-3-27B-ArliAI-RPMax-v3": { - id: "Gemma-3-27B-ArliAI-RPMax-v3", - name: "Gemma 3 27B RPMax v3", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-03", - last_updated: "2025-07-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "gemini-2.5-flash-preview-05-20:thinking": { - id: "gemini-2.5-flash-preview-05-20:thinking", - name: "Gemini 2.5 Flash 0520 Thinking", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-05-20", - last_updated: "2025-05-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 3.5 }, - limit: { context: 1048000, input: 1048000, output: 65536 }, - }, - "claude-sonnet-4-20250514": { - id: "claude-sonnet-4-20250514", - name: "Claude 4 Sonnet", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.994 }, - limit: { context: 200000, input: 200000, output: 64000 }, - }, - "claude-opus-4-1-thinking:32000": { - id: "claude-opus-4-1-thinking:32000", - name: "Claude 4.1 Opus Thinking (32K)", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 75.004 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "sarvan-medium": { - id: "sarvan-medium", - name: "Sarvam Medium", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.75 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "Llama-3.3-70B-Anthrobomination": { - id: "Llama-3.3-70B-Anthrobomination", - name: "Llama 3.3 70B Anthrobomination", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "venice-uncensored": { - id: "venice-uncensored", - name: "Venice Uncensored", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-24", - last_updated: "2025-02-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 0.4 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "Baichuan4-Air": { - id: "Baichuan4-Air", - name: "Baichuan 4 Air", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-19", - last_updated: "2025-08-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.157, output: 0.157 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - "jamba-mini": { - id: "jamba-mini", - name: "Jamba Mini", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1989, output: 0.408 }, - limit: { context: 256000, input: 256000, output: 4096 }, - }, - "KAT-Coder-Exp-72B-1010": { - id: "KAT-Coder-Exp-72B-1010", - name: "KAT Coder Exp 72B 1010", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-10-28", - last_updated: "2025-10-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.2 }, - limit: { context: 128000, input: 128000, output: 32768 }, - }, - "gemini-2.5-flash-preview-04-17:thinking": { - id: "gemini-2.5-flash-preview-04-17:thinking", - name: "Gemini 2.5 Flash Preview Thinking", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-04-17", - last_updated: "2025-04-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 3.5 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "brave-research": { - id: "brave-research", - name: "Brave (Research)", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2023-03-02", - last_updated: "2024-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 5 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "claude-opus-4-1-20250805": { - id: "claude-opus-4-1-20250805", - name: "Claude 4.1 Opus", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 14.994, output: 75.004 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "Llama-3.3-70B-Argunaut-1-SFT": { - id: "Llama-3.3-70B-Argunaut-1-SFT", - name: "Llama 3.3 70B Argunaut 1 SFT", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "claude-opus-4-5-20251101:thinking": { - id: "claude-opus-4-5-20251101:thinking", - name: "Claude 4.5 Opus Thinking", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-11-01", - last_updated: "2025-11-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 4.998, output: 25.007 }, - limit: { context: 200000, input: 200000, output: 32000 }, - }, - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - name: "Gemini 2.5 Pro", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-06-05", - last_updated: "2025-06-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "grok-3-beta": { - id: "grok-3-beta", - name: "Grok 3 Beta", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 131072, input: 131072, output: 131072 }, - }, - "azure-o3-mini": { - id: "azure-o3-mini", - name: "Azure o3-mini", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-31", - last_updated: "2025-01-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.088, output: 4.3996 }, - limit: { context: 200000, input: 200000, output: 65536 }, - }, - "QwQ-32B-ArliAI-RpR-v1": { - id: "QwQ-32B-ArliAI-RpR-v1", - name: "QwQ 32b Arli V1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - "Llama-3.3-70B-Forgotten-Abomination-v5.0": { - id: "Llama-3.3-70B-Forgotten-Abomination-v5.0", - name: "Llama 3.3 70B Forgotten Abomination v5.0", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "doubao-seed-2-0-code-preview-260215": { - id: "doubao-seed-2-0-code-preview-260215", - name: "Doubao Seed 2.0 Code Preview", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.782, output: 3.893 }, - limit: { context: 256000, input: 256000, output: 128000 }, - }, - "Llama-3.3-70B-Mhnnn-x1": { - id: "Llama-3.3-70B-Mhnnn-x1", - name: "Llama 3.3 70B Mhnnn x1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "hunyuan-t1-latest": { - id: "hunyuan-t1-latest", - name: "Hunyuan T1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-22", - last_updated: "2025-03-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.17, output: 0.66 }, - limit: { context: 256000, input: 256000, output: 16384 }, - }, - "Gemma-3-27B-CardProjector-v4": { - id: "Gemma-3-27B-CardProjector-v4", - name: "Gemma 3 27B CardProjector v4", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-10", - last_updated: "2025-03-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "glm-4-flash": { - id: "glm-4-flash", - name: "GLM-4 Flash", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-08-01", - last_updated: "2024-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1003, output: 0.1003 }, - limit: { context: 128000, input: 128000, output: 4096 }, - }, - "learnlm-1.5-pro-experimental": { - id: "learnlm-1.5-pro-experimental", - name: "Gemini LearnLM Experimental", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-05-14", - last_updated: "2024-05-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3.502, output: 10.506 }, - limit: { context: 32767, input: 32767, output: 8192 }, - }, - "Llama-3.3-70B-Dark-Ages-v0.1": { - id: "Llama-3.3-70B-Dark-Ages-v0.1", - name: "Llama 3.3 70B Dark Ages v0.1", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "yi-large": { - id: "yi-large", - name: "Yi Large", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3.196, output: 3.196 }, - limit: { context: 32000, input: 32000, output: 4096 }, - }, - "exa-answer": { - id: "exa-answer", - name: "Exa (Answer)", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-06-04", - last_updated: "2025-06-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 2.5 }, - limit: { context: 4096, input: 4096, output: 4096 }, - }, - "gemini-2.5-pro-exp-03-25": { - id: "gemini-2.5-pro-exp-03-25", - name: "Gemini 2.5 Pro Experimental 0325", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-03-25", - last_updated: "2025-03-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "abacusai/Dracarys-72B-Instruct": { - id: "abacusai/Dracarys-72B-Instruct", - name: "Llama 3.1 70B Dracarys 2", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-02", - last_updated: "2025-08-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "allenai/olmo-3-32b-think": { - id: "allenai/olmo-3-32b-think", - name: "Olmo 3 32B Think", - family: "allenai", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-01", - last_updated: "2025-11-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.44999999999999996 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "allenai/molmo-2-8b": { - id: "allenai/molmo-2-8b", - name: "Molmo 2 8B", - family: "allenai", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 36864, input: 36864, output: 36864 }, - }, - "allenai/olmo-3.1-32b-instruct": { - id: "allenai/olmo-3.1-32b-instruct", - name: "Olmo 3.1 32B Instruct", - family: "allenai", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-01-25", - last_updated: "2026-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.6 }, - limit: { context: 65536, input: 65536, output: 8192 }, - }, - "allenai/olmo-3.1-32b-think": { - id: "allenai/olmo-3.1-32b-think", - name: "Olmo 3.1 32B Think", - family: "allenai", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2026-01-25", - last_updated: "2026-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.5 }, - limit: { context: 65536, input: 65536, output: 8192 }, - }, - "nex-agi/deepseek-v3.1-nex-n1": { - id: "nex-agi/deepseek-v3.1-nex-n1", - name: "DeepSeek V3.1 Nex N1", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-10", - last_updated: "2025-12-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27999999999999997, output: 0.42000000000000004 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "Envoid 2/Llama-3.05-Nemotron-Tenyxchat-Storybreaker-70B": { - id: "Envoid 2/Llama-3.05-Nemotron-Tenyxchat-Storybreaker-70B", - name: "Nemotron Tenyxchat Storybreaker 70b", - family: "nemotron", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "Envoid 2/Llama-3.05-NT-Storybreaker-Ministral-70B": { - id: "Envoid 2/Llama-3.05-NT-Storybreaker-Ministral-70B", - name: "Llama 3.05 Storybreaker Ministral 70b", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "zai-org/glm-5": { - id: "zai-org/glm-5", - name: "GLM 5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 2.55 }, - limit: { context: 200000, input: 200000, output: 128000 }, - }, - "zai-org/glm-4.7-flash": { - id: "zai-org/glm-4.7-flash", - name: "GLM 4.7 Flash", - family: "glm-flash", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.4 }, - limit: { context: 200000, input: 200000, output: 128000 }, - }, - "zai-org/glm-4.7": { - id: "zai-org/glm-4.7", - name: "GLM 4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-01-29", - last_updated: "2026-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.8 }, - limit: { context: 200000, input: 200000, output: 128000 }, - }, - "zai-org/glm-5:thinking": { - id: "zai-org/glm-5:thinking", - name: "GLM 5 Thinking", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 2.55 }, - limit: { context: 200000, input: 200000, output: 128000 }, - }, - "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF": { - id: "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF", - name: "Nvidia Nemotron 70b", - family: "nemotron", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.357, output: 0.408 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "nvidia/Llama-3.3-Nemotron-Super-49B-v1": { - id: "nvidia/Llama-3.3-Nemotron-Super-49B-v1", - name: "Nvidia Nemotron Super 49B", - family: "nemotron", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-08", - last_updated: "2025-08-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "nvidia/nvidia-nemotron-nano-9b-v2": { - id: "nvidia/nvidia-nemotron-nano-9b-v2", - name: "Nvidia Nemotron Nano 9B v2", - family: "nemotron", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-18", - last_updated: "2025-08-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.17, output: 0.68 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "nvidia/Llama-3.1-Nemotron-Ultra-253B-v1": { - id: "nvidia/Llama-3.1-Nemotron-Ultra-253B-v1", - name: "Nvidia Nemotron Ultra 253B", - family: "nemotron", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-03", - last_updated: "2025-07-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 0.8 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "nvidia/nemotron-3-nano-30b-a3b": { - id: "nvidia/nemotron-3-nano-30b-a3b", - name: "Nvidia Nemotron 3 Nano 30B", - family: "nemotron", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-15", - last_updated: "2025-12-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.17, output: 0.68 }, - limit: { context: 256000, input: 256000, output: 262144 }, - }, - "nvidia/Llama-3_3-Nemotron-Super-49B-v1_5": { - id: "nvidia/Llama-3_3-Nemotron-Super-49B-v1_5", - name: "Nvidia Nemotron Super 49B v1.5", - family: "nemotron", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-08", - last_updated: "2025-08-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.25 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "arcee-ai/trinity-mini": { - id: "arcee-ai/trinity-mini", - name: "Trinity Mini", - family: "trinity-mini", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.045000000000000005, output: 0.15 }, - limit: { context: 131072, input: 131072, output: 8192 }, - }, - "arcee-ai/trinity-large": { - id: "arcee-ai/trinity-large", - name: "Trinity Large", - family: "trinity", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1 }, - limit: { context: 131072, input: 131072, output: 8192 }, - }, - "meganova-ai/manta-flash-1.0": { - id: "meganova-ai/manta-flash-1.0", - name: "Manta Flash 1.0", - family: "nova", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-20", - last_updated: "2025-12-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0.16 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "meganova-ai/manta-pro-1.0": { - id: "meganova-ai/manta-pro-1.0", - name: "Manta Pro 1.0", - family: "nova", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-20", - last_updated: "2025-12-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.060000000000000005, output: 0.5 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - "meganova-ai/manta-mini-1.0": { - id: "meganova-ai/manta-mini-1.0", - name: "Manta Mini 1.0", - family: "nova", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-20", - last_updated: "2025-12-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0.16 }, - limit: { context: 8192, input: 8192, output: 8192 }, - }, - "Alibaba-NLP 2/Tongyi-DeepResearch-30B-A3B": { - id: "Alibaba-NLP 2/Tongyi-DeepResearch-30B-A3B", - name: "Tongyi DeepResearch 30B A3B", - family: "yi", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-26", - last_updated: "2025-08-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.08, output: 0.24000000000000002 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "xiaomi/mimo-v2-flash-original": { - id: "xiaomi/mimo-v2-flash-original", - name: "MiMo V2 Flash Original", - family: "mimo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.102, output: 0.306 }, - limit: { context: 256000, input: 256000, output: 32768 }, - }, - "xiaomi/mimo-v2-flash": { - id: "xiaomi/mimo-v2-flash", - name: "MiMo V2 Flash", - family: "mimo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.102, output: 0.306 }, - limit: { context: 256000, input: 256000, output: 32768 }, - }, - "xiaomi/mimo-v2-flash-thinking": { - id: "xiaomi/mimo-v2-flash-thinking", - name: "MiMo V2 Flash (Thinking)", - family: "mimo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.102, output: 0.306 }, - limit: { context: 256000, input: 256000, output: 32768 }, - }, - "xiaomi/mimo-v2-flash-thinking-original": { - id: "xiaomi/mimo-v2-flash-thinking-original", - name: "MiMo V2 Flash (Thinking) Original", - family: "mimo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.102, output: 0.306 }, - limit: { context: 256000, input: 256000, output: 32768 }, - }, - "microsoft/MAI-DS-R1-FP8": { - id: "microsoft/MAI-DS-R1-FP8", - name: "Microsoft DeepSeek R1", - family: "deepseek", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.3 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "microsoft/wizardlm-2-8x22b": { - id: "microsoft/wizardlm-2-8x22b", - name: "WizardLM-2 8x22B", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 65536, input: 65536, output: 8192 }, - }, - "EVA-UNIT-01 2/EVA-Qwen2.5-72B-v0.2": { - id: "EVA-UNIT-01 2/EVA-Qwen2.5-72B-v0.2", - name: "EVA-Qwen2.5-72B-v0.2", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7989999999999999, output: 0.7989999999999999 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "EVA-UNIT-01 2/EVA-LLaMA-3.33-70B-v0.0": { - id: "EVA-UNIT-01 2/EVA-LLaMA-3.33-70B-v0.0", - name: "EVA Llama 3.33 70B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-26", - last_updated: "2025-07-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.006, output: 2.006 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "EVA-UNIT-01 2/EVA-LLaMA-3.33-70B-v0.1": { - id: "EVA-UNIT-01 2/EVA-LLaMA-3.33-70B-v0.1", - name: "EVA-LLaMA-3.33-70B-v0.1", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.006, output: 2.006 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "EVA-UNIT-01 2/EVA-Qwen2.5-32B-v0.2": { - id: "EVA-UNIT-01 2/EVA-Qwen2.5-32B-v0.2", - name: "EVA-Qwen2.5-32B-v0.2", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-26", - last_updated: "2025-07-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7989999999999999, output: 0.7989999999999999 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "failspy/Meta-Llama-3-70B-Instruct-abliterated-v3.5": { - id: "failspy/Meta-Llama-3-70B-Instruct-abliterated-v3.5", - name: "Llama 3 70B abliterated", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-26", - last_updated: "2025-07-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7, output: 0.7 }, - limit: { context: 8192, input: 8192, output: 8192 }, - }, - "featherless-ai/Qwerky-72B": { - id: "featherless-ai/Qwerky-72B", - name: "Qwerky 72B", - family: "qwerky", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-20", - last_updated: "2025-03-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 0.5 }, - limit: { context: 32000, input: 32000, output: 8192 }, - }, - "MiniMaxAI 2/MiniMax-M1-80k": { - id: "MiniMaxAI 2/MiniMax-M1-80k", - name: "MiniMax M1 80K", - family: "minimax", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-06-16", - last_updated: "2025-06-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6052, output: 2.4225000000000003 }, - limit: { context: 1000000, input: 1000000, output: 131072 }, - }, - "TEE/glm-5": { - id: "TEE/glm-5", - name: "GLM 5 TEE", - family: "glm", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.2, output: 3.5 }, - limit: { context: 203000, input: 203000, output: 65535 }, - }, - "TEE/deepseek-v3.1": { - id: "TEE/deepseek-v3.1", - name: "DeepSeek V3.1 TEE", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 2.5 }, - limit: { context: 164000, input: 164000, output: 8192 }, - }, - "TEE/glm-4.7-flash": { - id: "TEE/glm-4.7-flash", - name: "GLM 4.7 Flash TEE", - family: "glm-flash", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-01-19", - last_updated: "2026-01-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.5 }, - limit: { context: 203000, input: 203000, output: 65535 }, - }, - "TEE/qwen3-coder": { - id: "TEE/qwen3-coder", - name: "Qwen3 Coder 480B TEE", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 2 }, - limit: { context: 128000, input: 128000, output: 32768 }, - }, - "TEE/glm-4.6": { - id: "TEE/glm-4.6", - name: "GLM 4.6 TEE", - family: "glm", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.75, output: 2 }, - limit: { context: 203000, input: 203000, output: 65535 }, - }, - "TEE/deepseek-r1-0528": { - id: "TEE/deepseek-r1-0528", - name: "DeepSeek R1 0528 TEE", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 2 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "TEE/minimax-m2.1": { - id: "TEE/minimax-m2.1", - name: "MiniMax M2.1 TEE", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 200000, input: 200000, output: 131072 }, - }, - "TEE/qwen3.5-397b-a17b": { - id: "TEE/qwen3.5-397b-a17b", - name: "Qwen3.5 397B A17B TEE", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-02-28", - last_updated: "2026-02-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 3.6 }, - limit: { context: 258048, input: 258048, output: 65536 }, - }, - "TEE/gpt-oss-120b": { - id: "TEE/gpt-oss-120b", - name: "GPT-OSS 120B TEE", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 2 }, - limit: { context: 131072, input: 131072, output: 16384 }, - }, - "TEE/kimi-k2.5": { - id: "TEE/kimi-k2.5", - name: "Kimi K2.5 TEE", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-01-29", - last_updated: "2026-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.9 }, - limit: { context: 128000, input: 128000, output: 65535 }, - }, - "TEE/qwen3-30b-a3b-instruct-2507": { - id: "TEE/qwen3-30b-a3b-instruct-2507", - name: "Qwen3 30B A3B Instruct 2507 TEE", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-29", - last_updated: "2025-07-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.44999999999999996 }, - limit: { context: 262000, input: 262000, output: 32768 }, - }, - "TEE/kimi-k2.5-thinking": { - id: "TEE/kimi-k2.5-thinking", - name: "Kimi K2.5 Thinking TEE", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2026-01-29", - last_updated: "2026-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.9 }, - limit: { context: 128000, input: 128000, output: 65535 }, - }, - "TEE/qwen2.5-vl-72b-instruct": { - id: "TEE/qwen2.5-vl-72b-instruct", - name: "Qwen2.5 VL 72B TEE", - family: "qwen", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-01", - last_updated: "2025-02-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7, output: 0.7 }, - limit: { context: 65536, input: 65536, output: 8192 }, - }, - "TEE/deepseek-v3.2": { - id: "TEE/deepseek-v3.2", - name: "DeepSeek V3.2 TEE", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1 }, - limit: { context: 164000, input: 164000, output: 65536 }, - }, - "TEE/glm-4.7": { - id: "TEE/glm-4.7", - name: "GLM 4.7 TEE", - family: "glm", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-01-29", - last_updated: "2026-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.85, output: 3.3 }, - limit: { context: 131000, input: 131000, output: 65535 }, - }, - "TEE/kimi-k2-thinking": { - id: "TEE/kimi-k2-thinking", - name: "Kimi K2 Thinking TEE", - family: "kimi-thinking", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 2 }, - limit: { context: 128000, input: 128000, output: 65535 }, - }, - "TEE/llama3-3-70b": { - id: "TEE/llama3-3-70b", - name: "Llama 3.3 70B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-03", - last_updated: "2025-07-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 2 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "TEE/gemma-3-27b-it": { - id: "TEE/gemma-3-27b-it", - name: "Gemma 3 27B TEE", - family: "gemma", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-10", - last_updated: "2025-03-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 131072, input: 131072, output: 8192 }, - }, - "TEE/gpt-oss-20b": { - id: "TEE/gpt-oss-20b", - name: "GPT-OSS 20B TEE", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 131072, input: 131072, output: 8192 }, - }, - "amazon/nova-micro-v1": { - id: "amazon/nova-micro-v1", - name: "Amazon Nova Micro 1.0", - family: "nova-micro", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.0357, output: 0.1394 }, - limit: { context: 128000, input: 128000, output: 5120 }, - }, - "amazon/nova-lite-v1": { - id: "amazon/nova-lite-v1", - name: "Amazon Nova Lite 1.0", - family: "nova-lite", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.0595, output: 0.238 }, - limit: { context: 300000, input: 300000, output: 5120 }, - }, - "amazon/nova-2-lite-v1": { - id: "amazon/nova-2-lite-v1", - name: "Amazon Nova 2 Lite", - family: "nova", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5099999999999999, output: 4.25 }, - limit: { context: 1000000, input: 1000000, output: 65535 }, - }, - "amazon/nova-pro-v1": { - id: "amazon/nova-pro-v1", - name: "Amazon Nova Pro 1.0", - family: "nova-pro", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7989999999999999, output: 3.1959999999999997 }, - limit: { context: 300000, input: 300000, output: 32000 }, - }, - "Infermatic 2/MN-12B-Inferor-v0.0": { - id: "Infermatic 2/MN-12B-Inferor-v0.0", - name: "Mistral Nemo Inferor 12B", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25499999999999995, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "anthracite-org/magnum-v2-72b": { - id: "anthracite-org/magnum-v2-72b", - name: "Magnum V2 72B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.006, output: 2.992 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "anthracite-org/magnum-v4-72b": { - id: "anthracite-org/magnum-v4-72b", - name: "Magnum v4 72B", - family: "llama", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.006, output: 2.992 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "essentialai/rnj-1-instruct": { - id: "essentialai/rnj-1-instruct", - name: "RNJ-1 Instruct 8B", - family: "rnj", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-13", - last_updated: "2025-12-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "NousResearch 2/hermes-4-405b": { - id: "NousResearch 2/hermes-4-405b", - name: "Hermes 4 Large", - family: "nousresearch", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-08-26", - last_updated: "2025-08-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "NousResearch 2/hermes-3-llama-3.1-70b": { - id: "NousResearch 2/hermes-3-llama-3.1-70b", - name: "Hermes 3 70B", - family: "nousresearch", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-01-07", - last_updated: "2026-01-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.408, output: 0.408 }, - limit: { context: 65536, input: 65536, output: 8192 }, - }, - "NousResearch 2/DeepHermes-3-Mistral-24B-Preview": { - id: "NousResearch 2/DeepHermes-3-Mistral-24B-Preview", - name: "DeepHermes-3 Mistral 24B (Preview)", - family: "nousresearch", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-05-10", - last_updated: "2025-05-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.3 }, - limit: { context: 128000, input: 128000, output: 32768 }, - }, - "NousResearch 2/hermes-4-70b": { - id: "NousResearch 2/hermes-4-70b", - name: "Hermes 4 Medium", - family: "nousresearch", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-03", - last_updated: "2025-07-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2006, output: 0.39949999999999997 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "NousResearch 2/hermes-4-405b:thinking": { - id: "NousResearch 2/hermes-4-405b:thinking", - name: "Hermes 4 Large (Thinking)", - family: "nousresearch", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "NousResearch 2/Hermes-4-70B:thinking": { - id: "NousResearch 2/Hermes-4-70B:thinking", - name: "Hermes 4 (Thinking)", - family: "nousresearch", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-17", - last_updated: "2025-09-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2006, output: 0.39949999999999997 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "Sao10K 2/L3.1-70B-Hanami-x1": { - id: "Sao10K 2/L3.1-70B-Hanami-x1", - name: "Llama 3.1 70B Hanami", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "Sao10K 2/L3.3-70B-Euryale-v2.3": { - id: "Sao10K 2/L3.3-70B-Euryale-v2.3", - name: "Llama 3.3 70B Euryale", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 20480, input: 20480, output: 16384 }, - }, - "Sao10K 2/L3.1-70B-Euryale-v2.2": { - id: "Sao10K 2/L3.1-70B-Euryale-v2.2", - name: "Llama 3.1 70B Euryale", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.357 }, - limit: { context: 20480, input: 20480, output: 16384 }, - }, - "Sao10K 2/L3-8B-Stheno-v3.2": { - id: "Sao10K 2/L3-8B-Stheno-v3.2", - name: "Sao10K Stheno 8b", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-11-29", - last_updated: "2024-11-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2006, output: 0.2006 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "pamanseau/OpenReasoning-Nemotron-32B": { - id: "pamanseau/OpenReasoning-Nemotron-32B", - name: "OpenReasoning Nemotron 32B", - family: "nemotron", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 32768, input: 32768, output: 65536 }, - }, - "LLM360 2/K2-Think": { - id: "LLM360 2/K2-Think", - name: "K2-Think", - family: "kimi-thinking", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-26", - last_updated: "2025-07-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.17, output: 0.68 }, - limit: { context: 128000, input: 128000, output: 32768 }, - }, - "NeverSleep 2/Llama-3-Lumimaid-70B-v0.1": { - id: "NeverSleep 2/Llama-3-Lumimaid-70B-v0.1", - name: "Lumimaid 70b", - family: "llama", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.006, output: 2.006 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "NeverSleep 2/Lumimaid-v0.2-70B": { - id: "NeverSleep 2/Lumimaid-v0.2-70B", - name: "Lumimaid v0.2", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 1.5 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "deepseek-ai/deepseek-v3.2-exp-thinking": { - id: "deepseek-ai/deepseek-v3.2-exp-thinking", - name: "DeepSeek V3.2 Exp Thinking", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27999999999999997, output: 0.42000000000000004 }, - limit: { context: 163840, input: 163840, output: 65536 }, - }, - "deepseek-ai/DeepSeek-R1-0528": { - id: "deepseek-ai/DeepSeek-R1-0528", - name: "DeepSeek R1 0528", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.7 }, - limit: { context: 128000, input: 128000, output: 163840 }, - }, - "deepseek-ai/DeepSeek-V3.1:thinking": { - id: "deepseek-ai/DeepSeek-V3.1:thinking", - name: "DeepSeek V3.1 Thinking", - family: "deepseek-thinking", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.7 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "deepseek-ai/DeepSeek-V3.1": { - id: "deepseek-ai/DeepSeek-V3.1", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-26", - last_updated: "2025-07-26", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.7 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "deepseek-ai/DeepSeek-V3.1-Terminus": { - id: "deepseek-ai/DeepSeek-V3.1-Terminus", - name: "DeepSeek V3.1 Terminus", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-08-02", - last_updated: "2025-08-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.7 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "deepseek-ai/deepseek-v3.2-exp": { - id: "deepseek-ai/deepseek-v3.2-exp", - name: "DeepSeek V3.2 Exp", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27999999999999997, output: 0.42000000000000004 }, - limit: { context: 163840, input: 163840, output: 65536 }, - }, - "deepseek-ai/DeepSeek-V3.1-Terminus:thinking": { - id: "deepseek-ai/DeepSeek-V3.1-Terminus:thinking", - name: "DeepSeek V3.1 Terminus (Thinking)", - family: "deepseek-thinking", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-09-22", - last_updated: "2025-09-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 0.7 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "raifle/sorcererlm-8x22b": { - id: "raifle/sorcererlm-8x22b", - name: "SorcererLM 8x22B", - family: "mixtral", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 4.505, output: 4.505 }, - limit: { context: 16000, input: 16000, output: 8192 }, - }, - "Salesforce 2/Llama-xLAM-2-70b-fc-r": { - id: "Salesforce 2/Llama-xLAM-2-70b-fc-r", - name: "Llama-xLAM-2 70B fc-r", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-13", - last_updated: "2025-04-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 2.5 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "aion-labs/aion-1.0-mini": { - id: "aion-labs/aion-1.0-mini", - name: "Aion 1.0 mini (DeepSeek)", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-20", - last_updated: "2025-02-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7989999999999999, output: 1.394 }, - limit: { context: 131072, input: 131072, output: 8192 }, - }, - "aion-labs/aion-rp-llama-3.1-8b": { - id: "aion-labs/aion-rp-llama-3.1-8b", - name: "Llama 3.1 8b (uncensored)", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2006, output: 0.2006 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "aion-labs/aion-1.0": { - id: "aion-labs/aion-1.0", - name: "Aion 1.0", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-01", - last_updated: "2025-02-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3.995, output: 7.99 }, - limit: { context: 65536, input: 65536, output: 8192 }, - }, - "mlabonne/NeuralDaredevil-8B-abliterated": { - id: "mlabonne/NeuralDaredevil-8B-abliterated", - name: "Neural Daredevil 8B abliterated", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.44, output: 0.44 }, - limit: { context: 8192, input: 8192, output: 8192 }, - }, - "unsloth/gemma-3-1b-it": { - id: "unsloth/gemma-3-1b-it", - name: "Gemma 3 1B IT", - family: "unsloth", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-10", - last_updated: "2025-03-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1003, output: 0.1003 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "unsloth/gemma-3-12b-it": { - id: "unsloth/gemma-3-12b-it", - name: "Gemma 3 12B IT", - family: "unsloth", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-10", - last_updated: "2025-03-10", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.272, output: 0.272 }, - limit: { context: 128000, input: 128000, output: 131072 }, - }, - "unsloth/gemma-3-4b-it": { - id: "unsloth/gemma-3-4b-it", - name: "Gemma 3 4B IT", - family: "unsloth", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-10", - last_updated: "2025-03-10", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2006, output: 0.2006 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "unsloth/gemma-3-27b-it": { - id: "unsloth/gemma-3-27b-it", - name: "Gemma 3 27B IT", - family: "unsloth", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-10", - last_updated: "2025-03-10", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2992, output: 0.2992 }, - limit: { context: 128000, input: 128000, output: 96000 }, - }, - "meituan-longcat/LongCat-Flash-Chat-FP8": { - id: "meituan-longcat/LongCat-Flash-Chat-FP8", - name: "LongCat Flash", - family: "longcat", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-08-31", - last_updated: "2025-08-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.7 }, - limit: { context: 128000, input: 128000, output: 32768 }, - }, - "cognitivecomputations/dolphin-2.9.2-qwen2-72b": { - id: "cognitivecomputations/dolphin-2.9.2-qwen2-72b", - name: "Dolphin 72b", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-27", - last_updated: "2025-02-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.306, output: 0.306 }, - limit: { context: 8192, input: 8192, output: 4096 }, - }, - "tencent/Hunyuan-MT-7B": { - id: "tencent/Hunyuan-MT-7B", - name: "Hunyuan MT 7B", - family: "hunyuan", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-18", - last_updated: "2025-09-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 20 }, - limit: { context: 8192, input: 8192, output: 8192 }, - }, - "soob3123/Veiled-Calla-12B": { - id: "soob3123/Veiled-Calla-12B", - name: "Veiled Calla 12B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-13", - last_updated: "2025-04-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.3 }, - limit: { context: 32768, input: 32768, output: 8192 }, - }, - "soob3123/amoral-gemma3-27B-v2": { - id: "soob3123/amoral-gemma3-27B-v2", - name: "Amoral Gemma3 27B v2", - family: "gemma", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-05-23", - last_updated: "2025-05-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.3 }, - limit: { context: 32768, input: 32768, output: 8192 }, - }, - "soob3123/GrayLine-Qwen3-8B": { - id: "soob3123/GrayLine-Qwen3-8B", - name: "Grayline Qwen3 8B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.3 }, - limit: { context: 16384, input: 16384, output: 32768 }, - }, - "GalrionSoftworks 2/MN-LooseCannon-12B-v1": { - id: "GalrionSoftworks 2/MN-LooseCannon-12B-v1", - name: "MN-LooseCannon-12B-v1", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "deepseek/deepseek-prover-v2-671b": { - id: "deepseek/deepseek-prover-v2-671b", - name: "DeepSeek Prover v2 671B", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-30", - last_updated: "2025-04-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 2.5 }, - limit: { context: 160000, input: 160000, output: 16384 }, - }, - "deepseek/deepseek-v3.2-speciale": { - id: "deepseek/deepseek-v3.2-speciale", - name: "DeepSeek V3.2 Speciale", - family: "deepseek", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-12-02", - last_updated: "2025-12-02", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27999999999999997, output: 0.42000000000000004 }, - limit: { context: 163000, input: 163000, output: 65536 }, - }, - "deepseek/deepseek-v3.2": { - id: "deepseek/deepseek-v3.2", - name: "DeepSeek V3.2", - family: "deepseek", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27999999999999997, output: 0.42000000000000004 }, - limit: { context: 163000, input: 163000, output: 65536 }, - }, - "deepseek/deepseek-v3.2:thinking": { - id: "deepseek/deepseek-v3.2:thinking", - name: "DeepSeek V3.2 Thinking", - family: "deepseek", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27999999999999997, output: 0.42000000000000004 }, - limit: { context: 163000, input: 163000, output: 65536 }, - }, - "moonshotai/kimi-k2-instruct": { - id: "moonshotai/kimi-k2-instruct", - name: "Kimi K2 Instruct", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-07-01", - last_updated: "2025-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 2 }, - limit: { context: 256000, input: 256000, output: 8192 }, - }, - "moonshotai/kimi-k2.5:thinking": { - id: "moonshotai/kimi-k2.5:thinking", - name: "Kimi K2.5 Thinking", - family: "kimi-thinking", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - release_date: "2026-01-26", - last_updated: "2026-01-26", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.9 }, - limit: { context: 256000, input: 256000, output: 65536 }, - }, - "moonshotai/kimi-k2-thinking-turbo-original": { - id: "moonshotai/kimi-k2-thinking-turbo-original", - name: "Kimi K2 Thinking Turbo Original", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.15, output: 8 }, - limit: { context: 256000, input: 256000, output: 16384 }, - }, - "moonshotai/Kimi-K2-Instruct-0905": { - id: "moonshotai/Kimi-K2-Instruct-0905", - name: "Kimi K2 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 256000, input: 256000, output: 262144 }, - }, - "moonshotai/kimi-k2-instruct-0711": { - id: "moonshotai/kimi-k2-instruct-0711", - name: "Kimi K2 0711", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-07-11", - last_updated: "2025-07-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 2 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "moonshotai/Kimi-Dev-72B": { - id: "moonshotai/Kimi-Dev-72B", - name: "Kimi Dev 72B", - family: "kimi", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 0.4 }, - limit: { context: 128000, input: 128000, output: 131072 }, - }, - "moonshotai/kimi-k2.5": { - id: "moonshotai/kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: false, - release_date: "2026-01-26", - last_updated: "2026-01-26", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.9 }, - limit: { context: 256000, input: 256000, output: 65536 }, - }, - "moonshotai/kimi-k2-thinking": { - id: "moonshotai/kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 256000, input: 256000, output: 262144 }, - }, - "moonshotai/kimi-k2-thinking-original": { - id: "moonshotai/kimi-k2-thinking-original", - name: "Kimi K2 Thinking Original", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 2.5 }, - limit: { context: 256000, input: 256000, output: 16384 }, - }, - "MarinaraSpaghetti 2/NemoMix-Unleashed-12B": { - id: "MarinaraSpaghetti 2/NemoMix-Unleashed-12B", - name: "NemoMix 12B Unleashed", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 32768, input: 32768, output: 8192 }, - }, - "baidu/ernie-4.5-vl-28b-a3b": { - id: "baidu/ernie-4.5-vl-28b-a3b", - name: "ERNIE 4.5 VL 28B", - family: "ernie", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-06-30", - last_updated: "2025-06-30", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.13999999999999999, output: 0.5599999999999999 }, - limit: { context: 32768, input: 32768, output: 16384 }, - }, - "baidu/ernie-4.5-300b-a47b": { - id: "baidu/ernie-4.5-300b-a47b", - name: "ERNIE 4.5 300B", - family: "ernie", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-06-30", - last_updated: "2025-06-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.35, output: 1.15 }, - limit: { context: 131072, input: 131072, output: 16384 }, - }, - "google/gemini-flash-1.5": { - id: "google/gemini-flash-1.5", - name: "Gemini 1.5 Flash", - family: "gemini-flash", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-05-14", - last_updated: "2024-05-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.0748, output: 0.306 }, - limit: { context: 2000000, input: 2000000, output: 8192 }, - }, - "google/gemini-3-flash-preview": { - id: "google/gemini-3-flash-preview", - name: "Gemini 3 Flash (Preview)", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 3 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "google/gemini-3-flash-preview-thinking": { - id: "google/gemini-3-flash-preview-thinking", - name: "Gemini 3 Flash Thinking", - family: "gemini-flash", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-12-17", - last_updated: "2025-12-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 3 }, - limit: { context: 1048756, input: 1048756, output: 65536 }, - }, - "z-ai/glm-4.6:thinking": { - id: "z-ai/glm-4.6:thinking", - name: "GLM 4.6 Thinking", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.5 }, - limit: { context: 200000, input: 200000, output: 65535 }, - }, - "z-ai/glm-4.5v:thinking": { - id: "z-ai/glm-4.5v:thinking", - name: "GLM 4.5V Thinking", - family: "glmv", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-22", - last_updated: "2025-11-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 1.7999999999999998 }, - limit: { context: 64000, input: 64000, output: 96000 }, - }, - "z-ai/glm-4.6": { - id: "z-ai/glm-4.6", - name: "GLM 4.6", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-09-30", - last_updated: "2025-09-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.5 }, - limit: { context: 200000, input: 200000, output: 65535 }, - }, - "z-ai/glm-4.5v": { - id: "z-ai/glm-4.5v", - name: "GLM 4.5V", - family: "glmv", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-22", - last_updated: "2025-11-22", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 1.7999999999999998 }, - limit: { context: 64000, input: 64000, output: 96000 }, - }, - "Tongyi-Zhiwen 2/QwenLong-L1-32B": { - id: "Tongyi-Zhiwen 2/QwenLong-L1-32B", - name: "QwenLong L1 32B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-25", - last_updated: "2025-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.13999999999999999, output: 0.6 }, - limit: { context: 128000, input: 128000, output: 40960 }, - }, - "stepfun-ai/step-3.5-flash:thinking": { - id: "stepfun-ai/step-3.5-flash:thinking", - name: "Step 3.5 Flash Thinking", - family: "step", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2026-02-02", - last_updated: "2026-02-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5 }, - limit: { context: 256000, input: 256000, output: 256000 }, - }, - "stepfun-ai/step-3.5-flash": { - id: "stepfun-ai/step-3.5-flash", - name: "Step 3.5 Flash", - family: "step", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2026-02-02", - last_updated: "2026-02-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5 }, - limit: { context: 256000, input: 256000, output: 256000 }, - }, - "deepcogito/cogito-v2.1-671b": { - id: "deepcogito/cogito-v2.1-671b", - name: "Cogito v2.1 671B MoE", - family: "cogito", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 1.25 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "deepcogito/cogito-v1-preview-qwen-32B": { - id: "deepcogito/cogito-v1-preview-qwen-32B", - name: "Cogito v1 Preview Qwen 32B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-05-10", - last_updated: "2025-05-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.7999999999999998, output: 1.7999999999999998 }, - limit: { context: 128000, input: 128000, output: 32768 }, - }, - "undi95/remm-slerp-l2-13b": { - id: "undi95/remm-slerp-l2-13b", - name: "ReMM SLERP 13B", - family: "llama", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7989999999999999, output: 1.2069999999999999 }, - limit: { context: 6144, input: 6144, output: 4096 }, - }, - "qwen/qwen3.5-397b-a17b": { - id: "qwen/qwen3.5-397b-a17b", - name: "Qwen3.5 397B A17B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-02-16", - last_updated: "2026-02-16", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3.6 }, - limit: { context: 258048, input: 258048, output: 65536 }, - }, - "CrucibleLab 2/L3.3-70B-Loki-V2.0": { - id: "CrucibleLab 2/L3.3-70B-Loki-V2.0", - name: "L3.3 70B Loki v2.0", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-01-22", - last_updated: "2026-01-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "inflatebot/MN-12B-Mag-Mell-R1": { - id: "inflatebot/MN-12B-Mag-Mell-R1", - name: "Mag Mell R1", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "nothingiisreal/L3.1-70B-Celeste-V0.1-BF16": { - id: "nothingiisreal/L3.1-70B-Celeste-V0.1-BF16", - name: "Llama 3.1 70B Celeste v0.1", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "x-ai/grok-4-fast:thinking": { - id: "x-ai/grok-4-fast:thinking", - name: "Grok 4 Fast Thinking", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5 }, - limit: { context: 2000000, input: 2000000, output: 131072 }, - }, - "x-ai/grok-code-fast-1": { - id: "x-ai/grok-code-fast-1", - name: "Grok Code Fast 1", - family: "grok", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-08-28", - last_updated: "2025-08-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.5 }, - limit: { context: 256000, input: 256000, output: 131072 }, - }, - "x-ai/grok-4.1-fast-reasoning": { - id: "x-ai/grok-4.1-fast-reasoning", - name: "Grok 4.1 Fast Reasoning", - family: "grok", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-20", - last_updated: "2025-11-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5 }, - limit: { context: 2000000, input: 2000000, output: 131072 }, - }, - "x-ai/grok-4-fast": { - id: "x-ai/grok-4-fast", - name: "Grok 4 Fast", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-09-20", - last_updated: "2025-09-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5 }, - limit: { context: 2000000, input: 2000000, output: 131072 }, - }, - "x-ai/grok-4.1-fast": { - id: "x-ai/grok-4.1-fast", - name: "Grok 4.1 Fast", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-11-20", - last_updated: "2025-11-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5 }, - limit: { context: 2000000, input: 2000000, output: 131072 }, - }, - "x-ai/grok-4-07-09": { - id: "x-ai/grok-4-07-09", - name: "Grok 4", - family: "grok", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15 }, - limit: { context: 256000, input: 256000, output: 131072 }, - }, - "Gryphe 2/MythoMax-L2-13b": { - id: "Gryphe 2/MythoMax-L2-13b", - name: "MythoMax 13B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-08", - last_updated: "2025-08-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1003, output: 0.1003 }, - limit: { context: 4000, input: 4000, output: 4096 }, - }, - "meta-llama/llama-4-scout": { - id: "meta-llama/llama-4-scout", - name: "Llama 4 Scout", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.085, output: 0.46 }, - limit: { context: 328000, input: 328000, output: 65536 }, - }, - "meta-llama/llama-3.2-90b-vision-instruct": { - id: "meta-llama/llama-3.2-90b-vision-instruct", - name: "Llama 3.2 Medium", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.9009999999999999, output: 0.9009999999999999 }, - limit: { context: 131072, input: 131072, output: 16384 }, - }, - "meta-llama/llama-3.3-70b-instruct": { - id: "meta-llama/llama-3.3-70b-instruct", - name: "Llama 3.3 70b Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-02-27", - last_updated: "2025-02-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.23 }, - limit: { context: 131072, input: 131072, output: 16384 }, - }, - "meta-llama/llama-3.2-3b-instruct": { - id: "meta-llama/llama-3.2-3b-instruct", - name: "Llama 3.2 3b Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.0306, output: 0.0493 }, - limit: { context: 131072, input: 131072, output: 8192 }, - }, - "meta-llama/llama-4-maverick": { - id: "meta-llama/llama-4-maverick", - name: "Llama 4 Maverick", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.18000000000000002, output: 0.8 }, - limit: { context: 1048576, input: 1048576, output: 65536 }, - }, - "meta-llama/llama-3.1-8b-instruct": { - id: "meta-llama/llama-3.1-8b-instruct", - name: "Llama 3.1 8b Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.0544, output: 0.0544 }, - limit: { context: 131072, input: 131072, output: 16384 }, - }, - "tngtech/DeepSeek-TNG-R1T2-Chimera": { - id: "tngtech/DeepSeek-TNG-R1T2-Chimera", - name: "DeepSeek TNG R1T2 Chimera", - family: "tngtech", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.31, output: 0.31 }, - limit: { context: 128000, input: 128000, output: 8192 }, - }, - "tngtech/tng-r1t-chimera": { - id: "tngtech/tng-r1t-chimera", - name: "TNG R1T Chimera", - family: "tngtech", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-11-26", - last_updated: "2025-11-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "mistralai/ministral-3b-2512": { - id: "mistralai/ministral-3b-2512", - name: "Ministral 3B", - family: "ministral", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-04", - last_updated: "2025-12-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 131072, input: 131072, output: 32768 }, - }, - "mistralai/mistral-saba": { - id: "mistralai/mistral-saba", - name: "Mistral Saba", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1989, output: 0.595 }, - limit: { context: 32000, input: 32000, output: 32768 }, - }, - "mistralai/mistral-medium-3": { - id: "mistralai/mistral-medium-3", - name: "Mistral Medium 3", - family: "mistral-medium", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-25", - last_updated: "2025-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 131072, input: 131072, output: 32768 }, - }, - "mistralai/Mistral-Nemo-Instruct-2407": { - id: "mistralai/Mistral-Nemo-Instruct-2407", - name: "Mistral Nemo", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1003, output: 0.1207 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "mistralai/codestral-2508": { - id: "mistralai/codestral-2508", - name: "Codestral 2508", - family: "codestral", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-01", - last_updated: "2025-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.8999999999999999 }, - limit: { context: 256000, input: 256000, output: 32768 }, - }, - "mistralai/mistral-large-3-675b-instruct-2512": { - id: "mistralai/mistral-large-3-675b-instruct-2512", - name: "Mistral Large 3 675B", - family: "mistral-large", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-02", - last_updated: "2025-12-02", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 3 }, - limit: { context: 262144, input: 262144, output: 256000 }, - }, - "mistralai/mistral-small-creative": { - id: "mistralai/mistral-small-creative", - name: "Mistral Small Creative", - family: "mistral-small", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-12-16", - last_updated: "2025-12-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - "mistralai/ministral-8b-2512": { - id: "mistralai/ministral-8b-2512", - name: "Ministral 8B", - family: "ministral", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-04", - last_updated: "2025-12-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 262144, input: 262144, output: 32768 }, - }, - "mistralai/mixtral-8x22b-instruct-v0.1": { - id: "mistralai/mixtral-8x22b-instruct-v0.1", - name: "Mixtral 8x22B", - family: "mixtral", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.8999999999999999, output: 0.8999999999999999 }, - limit: { context: 65536, input: 65536, output: 32768 }, - }, - "mistralai/ministral-14b-2512": { - id: "mistralai/ministral-14b-2512", - name: "Ministral 14B", - family: "ministral", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-04", - last_updated: "2025-12-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 262144, input: 262144, output: 32768 }, - }, - "mistralai/ministral-14b-instruct-2512": { - id: "mistralai/ministral-14b-instruct-2512", - name: "Ministral 3 14B", - family: "ministral", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-02", - last_updated: "2025-12-02", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 262144, input: 262144, output: 32768 }, - }, - "mistralai/Devstral-Small-2505": { - id: "mistralai/Devstral-Small-2505", - name: "Mistral Devstral Small 2505", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-02", - last_updated: "2025-08-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.060000000000000005, output: 0.060000000000000005 }, - limit: { context: 32768, input: 32768, output: 8192 }, - }, - "mistralai/mistral-tiny": { - id: "mistralai/mistral-tiny", - name: "Mistral Tiny", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2023-12-11", - last_updated: "2024-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25499999999999995, output: 0.25499999999999995 }, - limit: { context: 32000, input: 32000, output: 8192 }, - }, - "mistralai/mistral-7b-instruct": { - id: "mistralai/mistral-7b-instruct", - name: "Mistral 7B Instruct", - family: "mistral", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-05-27", - last_updated: "2024-05-27", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.0544, output: 0.0544 }, - limit: { context: 32768, input: 32768, output: 8192 }, - }, - "mistralai/devstral-2-123b-instruct-2512": { - id: "mistralai/devstral-2-123b-instruct-2512", - name: "Devstral 2 123B", - family: "devstral", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-09", - last_updated: "2025-12-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.4 }, - limit: { context: 262144, input: 262144, output: 65536 }, - }, - "mistralai/mistral-large": { - id: "mistralai/mistral-large", - name: "Mistral Large 2411", - family: "mistral-large", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-02-26", - last_updated: "2024-02-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.006, output: 6.001 }, - limit: { context: 128000, input: 128000, output: 256000 }, - }, - "mistralai/mixtral-8x7b-instruct-v0.1": { - id: "mistralai/mixtral-8x7b-instruct-v0.1", - name: "Mixtral 8x7B", - family: "mixtral", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.27 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - "mistralai/mistral-medium-3.1": { - id: "mistralai/mistral-medium-3.1", - name: "Mistral Medium 3.1", - family: "mistral-medium", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-05", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 131072, input: 131072, output: 32768 }, - }, - "LatitudeGames 2/Wayfarer-Large-70B-Llama-3.3": { - id: "LatitudeGames 2/Wayfarer-Large-70B-Llama-3.3", - name: "Llama 3.3 70B Wayfarer", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-20", - last_updated: "2025-02-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.700000007, output: 0.700000007 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "openai/gpt-4o-2024-11-20": { - id: "openai/gpt-4o-2024-11-20", - name: "GPT-4o (2024-11-20)", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-11-20", - last_updated: "2024-11-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "openai/gpt-5.1-2025-11-13": { - id: "openai/gpt-5.1-2025-11-13", - name: "GPT-5.1 (2025-11-13)", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 1000000, input: 1000000, output: 32768 }, - }, - "openai/gpt-5-codex": { - id: "openai/gpt-5-codex", - name: "GPT-5 Codex", - family: "gpt-codex", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 9.996, output: 19.992 }, - limit: { context: 256000, input: 256000, output: 32768 }, - }, - "openai/gpt-5-pro": { - id: "openai/gpt-5-pro", - name: "GPT 5 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 120 }, - limit: { context: 400000, input: 400000, output: 128000 }, - }, - "openai/gpt-4o-mini": { - id: "openai/gpt-4o-mini", - name: "GPT-4o mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1496, output: 0.595 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "openai/gpt-5-chat-latest": { - id: "openai/gpt-5-chat-latest", - name: "GPT 5 Chat", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, input: 400000, output: 128000 }, - }, - "openai/gpt-4o-mini-search-preview": { - id: "openai/gpt-4o-mini-search-preview", - name: "GPT-4o mini Search Preview", - family: "gpt-mini", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.088, output: 0.35 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "openai/gpt-5.1-codex-max": { - id: "openai/gpt-5.1-codex-max", - name: "GPT 5.1 Codex Max", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 20 }, - limit: { context: 400000, input: 400000, output: 128000 }, - }, - "openai/gpt-5.2-codex": { - id: "openai/gpt-5.2-codex", - name: "GPT 5.2 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-01-14", - last_updated: "2026-01-14", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 400000, input: 400000, output: 128000 }, - }, - "openai/o3-deep-research": { - id: "openai/o3-deep-research", - name: "OpenAI o3 Deep Research", - family: "o", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 9.996, output: 19.992 }, - limit: { context: 200000, input: 200000, output: 100000 }, - }, - "openai/o1": { - id: "openai/o1", - name: "OpenAI o1", - family: "o", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2024-12-17", - last_updated: "2024-12-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 14.993999999999998, output: 59.993 }, - limit: { context: 200000, input: 200000, output: 100000 }, - }, - "openai/gpt-5.1": { - id: "openai/gpt-5.1", - name: "GPT 5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, input: 400000, output: 128000 }, - }, - "openai/gpt-5.2-chat": { - id: "openai/gpt-5.2-chat", - name: "GPT 5.2 Chat", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2026-01-01", - last_updated: "2026-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 400000, input: 400000, output: 16384 }, - }, - "openai/o4-mini-deep-research": { - id: "openai/o4-mini-deep-research", - name: "OpenAI o4-mini Deep Research", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 9.996, output: 19.992 }, - limit: { context: 200000, input: 200000, output: 100000 }, - }, - "openai/gpt-5.1-chat": { - id: "openai/gpt-5.1-chat", - name: "GPT 5.1 Chat", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, input: 400000, output: 128000 }, - }, - "openai/o3": { - id: "openai/o3", - name: "OpenAI o3", - family: "o", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8 }, - limit: { context: 200000, input: 200000, output: 100000 }, - }, - "openai/gpt-4-turbo-preview": { - id: "openai/gpt-4-turbo-preview", - name: "GPT-4 Turbo Preview", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2023-11-06", - last_updated: "2024-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 9.996, output: 30.004999999999995 }, - limit: { context: 128000, input: 128000, output: 4096 }, - }, - "openai/gpt-4.1-nano": { - id: "openai/gpt-4.1-nano", - name: "GPT 4.1 Nano", - family: "gpt-nano", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4 }, - limit: { context: 1047576, input: 1047576, output: 32768 }, - }, - "openai/gpt-3.5-turbo": { - id: "openai/gpt-3.5-turbo", - name: "GPT-3.5 Turbo", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2022-11-30", - last_updated: "2024-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 16385, input: 16385, output: 4096 }, - }, - "openai/gpt-oss-120b": { - id: "openai/gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.25 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "openai/gpt-5.1-codex-mini": { - id: "openai/gpt-5.1-codex-mini", - name: "GPT 5.1 Codex Mini", - family: "gpt-codex-mini", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2 }, - limit: { context: 400000, input: 400000, output: 128000 }, - }, - "openai/gpt-5.2": { - id: "openai/gpt-5.2", - name: "GPT 5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-01-01", - last_updated: "2026-01-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14 }, - limit: { context: 400000, input: 400000, output: 128000 }, - }, - "openai/gpt-4.1": { - id: "openai/gpt-4.1", - name: "GPT 4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2025-09-10", - last_updated: "2025-09-10", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8 }, - limit: { context: 1047576, input: 1047576, output: 32768 }, - }, - "openai/o3-mini-low": { - id: "openai/o3-mini-low", - name: "OpenAI o3-mini (Low)", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-01-31", - last_updated: "2025-01-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 9.996, output: 19.992 }, - limit: { context: 200000, input: 200000, output: 100000 }, - }, - "openai/gpt-4-turbo": { - id: "openai/gpt-4-turbo", - name: "GPT-4 Turbo", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2023-11-06", - last_updated: "2024-01-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 30 }, - limit: { context: 128000, input: 128000, output: 4096 }, - }, - "openai/gpt-5": { - id: "openai/gpt-5", - name: "GPT 5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, input: 400000, output: 128000 }, - }, - "openai/o4-mini": { - id: "openai/o4-mini", - name: "OpenAI o4-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4 }, - limit: { context: 200000, input: 200000, output: 100000 }, - }, - "openai/gpt-4.1-mini": { - id: "openai/gpt-4.1-mini", - name: "GPT 4.1 Mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.6 }, - limit: { context: 1047576, input: 1047576, output: 32768 }, - }, - "openai/o1-preview": { - id: "openai/o1-preview", - name: "OpenAI o1-preview", - family: "o", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2024-09-12", - last_updated: "2024-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 14.993999999999998, output: 59.993 }, - limit: { context: 128000, input: 128000, output: 32768 }, - }, - "openai/gpt-oss-safeguard-20b": { - id: "openai/gpt-oss-safeguard-20b", - name: "GPT OSS Safeguard 20B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-10-29", - last_updated: "2025-10-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "openai/o1-pro": { - id: "openai/o1-pro", - name: "OpenAI o1 Pro", - family: "o-pro", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-25", - last_updated: "2025-01-25", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 150, output: 600 }, - limit: { context: 200000, input: 200000, output: 100000 }, - }, - "openai/gpt-5.1-codex": { - id: "openai/gpt-5.1-codex", - name: "GPT 5.1 Codex", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, input: 400000, output: 128000 }, - }, - "openai/chatgpt-4o-latest": { - id: "openai/chatgpt-4o-latest", - name: "ChatGPT 4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 4.998, output: 14.993999999999998 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "openai/gpt-5.2-pro": { - id: "openai/gpt-5.2-pro", - name: "GPT 5.2 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-01-01", - last_updated: "2026-01-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 21, output: 168 }, - limit: { context: 400000, input: 400000, output: 128000 }, - }, - "openai/o3-mini": { - id: "openai/o3-mini", - name: "OpenAI o3-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-01-31", - last_updated: "2025-01-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4 }, - limit: { context: 200000, input: 200000, output: 100000 }, - }, - "openai/gpt-4o-2024-08-06": { - id: "openai/gpt-4o-2024-08-06", - name: "GPT-4o (2024-08-06)", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-08-06", - last_updated: "2024-08-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.499, output: 9.996 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "openai/gpt-5-mini": { - id: "openai/gpt-5-mini", - name: "GPT 5 Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2 }, - limit: { context: 400000, input: 400000, output: 128000 }, - }, - "openai/o3-pro-2025-06-10": { - id: "openai/o3-pro-2025-06-10", - name: "OpenAI o3-pro (2025-06-10)", - family: "o-pro", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-06-10", - last_updated: "2025-06-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 9.996, output: 19.992 }, - limit: { context: 200000, input: 200000, output: 100000 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "GPT OSS 20B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.04, output: 0.15 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "openai/gpt-5.1-chat-latest": { - id: "openai/gpt-5.1-chat-latest", - name: "GPT 5.1 Chat (Latest)", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10 }, - limit: { context: 400000, input: 400000, output: 16384 }, - }, - "openai/gpt-5-nano": { - id: "openai/gpt-5-nano", - name: "GPT 5 Nano", - family: "gpt-nano", - attachment: true, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4 }, - limit: { context: 400000, input: 400000, output: 128000 }, - }, - "openai/o3-mini-high": { - id: "openai/o3-mini-high", - name: "OpenAI o3-mini (High)", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-01-31", - last_updated: "2025-01-31", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.64, output: 2.588 }, - limit: { context: 200000, input: 200000, output: 100000 }, - }, - "openai/o4-mini-high": { - id: "openai/o4-mini-high", - name: "OpenAI o4-mini high", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4 }, - limit: { context: 200000, input: 200000, output: 100000 }, - }, - "openai/gpt-4o": { - id: "openai/gpt-4o", - name: "GPT-4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.499, output: 9.996 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "openai/gpt-4o-search-preview": { - id: "openai/gpt-4o-search-preview", - name: "GPT-4o Search Preview", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.47, output: 5.88 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "cohere/command-r-plus-08-2024": { - id: "cohere/command-r-plus-08-2024", - name: "Cohere: Command R+", - family: "command-r", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-08-30", - last_updated: "2024-08-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.856, output: 14.246 }, - limit: { context: 128000, input: 128000, output: 4096 }, - }, - "cohere/command-r": { - id: "cohere/command-r", - name: "Cohere: Command R", - family: "command-r", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-03-11", - last_updated: "2024-03-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.476, output: 1.428 }, - limit: { context: 128000, input: 128000, output: 4096 }, - }, - "THUDM/GLM-4-32B-0414": { - id: "THUDM/GLM-4-32B-0414", - name: "GLM 4 32B 0414", - family: "glm", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "THUDM/GLM-4-9B-0414": { - id: "THUDM/GLM-4-9B-0414", - name: "GLM 4 9B 0414", - family: "glm", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 32000, input: 32000, output: 8000 }, - }, - "THUDM/GLM-Z1-32B-0414": { - id: "THUDM/GLM-Z1-32B-0414", - name: "GLM Z1 32B 0414", - family: "glm-z", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 128000, input: 128000, output: 65536 }, - }, - "THUDM/GLM-Z1-9B-0414": { - id: "THUDM/GLM-Z1-9B-0414", - name: "GLM Z1 9B 0414", - family: "glm-z", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 32000, input: 32000, output: 8000 }, - }, - "THUDM/GLM-Z1-Rumination-32B-0414": { - id: "THUDM/GLM-Z1-Rumination-32B-0414", - name: "GLM Z1 Rumination 32B 0414", - family: "glm-z", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.2 }, - limit: { context: 32000, input: 32000, output: 65536 }, - }, - "minimax/minimax-01": { - id: "minimax/minimax-01", - name: "MiniMax 01", - family: "minimax", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-15", - last_updated: "2025-01-15", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1394, output: 1.1219999999999999 }, - limit: { context: 1000192, input: 1000192, output: 16384 }, - }, - "minimax/minimax-m2.1": { - id: "minimax/minimax-m2.1", - name: "MiniMax M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2025-12-19", - last_updated: "2025-12-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.33, output: 1.32 }, - limit: { context: 200000, input: 200000, output: 131072 }, - }, - "minimax/minimax-m2-her": { - id: "minimax/minimax-m2-her", - name: "MiniMax M2-her", - family: "minimax", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-01-24", - last_updated: "2026-01-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.30200000000000005, output: 1.2069999999999999 }, - limit: { context: 65532, input: 65532, output: 2048 }, - }, - "minimax/minimax-m2.5": { - id: "minimax/minimax-m2.5", - name: "MiniMax M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 204800, input: 204800, output: 131072 }, - }, - "chutesai/Mistral-Small-3.2-24B-Instruct-2506": { - id: "chutesai/Mistral-Small-3.2-24B-Instruct-2506", - name: "Mistral Small 3.2 24b Instruct", - family: "chutesai", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.4 }, - limit: { context: 128000, input: 128000, output: 131072 }, - }, - "baseten/Kimi-K2-Instruct-FP4": { - id: "baseten/Kimi-K2-Instruct-FP4", - name: "Kimi K2 0711 Instruct FP4", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-11", - last_updated: "2025-07-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 2 }, - limit: { context: 128000, input: 128000, output: 131072 }, - }, - "ReadyArt 2/The-Omega-Abomination-L-70B-v1.0": { - id: "ReadyArt 2/The-Omega-Abomination-L-70B-v1.0", - name: "The Omega Abomination V1", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7, output: 0.95 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "ReadyArt 2/MS3.2-The-Omega-Directive-24B-Unslop-v2.0": { - id: "ReadyArt 2/MS3.2-The-Omega-Directive-24B-Unslop-v2.0", - name: "Omega Directive 24B Unslop v2.0", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 0.5 }, - limit: { context: 16384, input: 16384, output: 32768 }, - }, - "TheDrummer 2/Cydonia-24B-v4.3": { - id: "TheDrummer 2/Cydonia-24B-v4.3", - name: "The Drummer Cydonia 24B v4.3", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-25", - last_updated: "2025-12-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1003, output: 0.1207 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - "TheDrummer 2/Anubis-70B-v1": { - id: "TheDrummer 2/Anubis-70B-v1", - name: "Anubis 70B v1", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.31, output: 0.31 }, - limit: { context: 65536, input: 65536, output: 16384 }, - }, - "TheDrummer 2/Cydonia-24B-v4": { - id: "TheDrummer 2/Cydonia-24B-v4", - name: "The Drummer Cydonia 24B v4", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-22", - last_updated: "2025-07-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2006, output: 0.2414 }, - limit: { context: 16384, input: 16384, output: 32768 }, - }, - "TheDrummer 2/Magidonia-24B-v4.3": { - id: "TheDrummer 2/Magidonia-24B-v4.3", - name: "The Drummer Magidonia 24B v4.3", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-12-25", - last_updated: "2025-12-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1003, output: 0.1207 }, - limit: { context: 32768, input: 32768, output: 32768 }, - }, - "TheDrummer 2/Anubis-70B-v1.1": { - id: "TheDrummer 2/Anubis-70B-v1.1", - name: "Anubis 70B v1.1", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.31, output: 0.31 }, - limit: { context: 131072, input: 131072, output: 16384 }, - }, - "TheDrummer 2/Rocinante-12B-v1.1": { - id: "TheDrummer 2/Rocinante-12B-v1.1", - name: "Rocinante 12b", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.408, output: 0.595 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "TheDrummer 2/Cydonia-24B-v2": { - id: "TheDrummer 2/Cydonia-24B-v2", - name: "The Drummer Cydonia 24B v2", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1003, output: 0.1207 }, - limit: { context: 16384, input: 16384, output: 32768 }, - }, - "TheDrummer 2/skyfall-36b-v2": { - id: "TheDrummer 2/skyfall-36b-v2", - name: "TheDrummer Skyfall 36B V2", - family: "llama", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-03-10", - last_updated: "2025-03-10", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 64000, input: 64000, output: 32768 }, - }, - "TheDrummer 2/UnslopNemo-12B-v4.1": { - id: "TheDrummer 2/UnslopNemo-12B-v4.1", - name: "UnslopNemo 12b v4", - family: "llama", - attachment: true, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 32768, input: 32768, output: 8192 }, - }, - "TheDrummer 2/Cydonia-24B-v4.1": { - id: "TheDrummer 2/Cydonia-24B-v4.1", - name: "The Drummer Cydonia 24B v4.1", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-19", - last_updated: "2025-08-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1003, output: 0.1207 }, - limit: { context: 16384, input: 16384, output: 32768 }, - }, - "Steelskull 2/L3.3-Electra-R1-70b": { - id: "Steelskull 2/L3.3-Electra-R1-70b", - name: "Steelskull Electra R1 70b", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.69989, output: 0.69989 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "Steelskull 2/L3.3-MS-Evalebis-70b": { - id: "Steelskull 2/L3.3-MS-Evalebis-70b", - name: "MS Evalebis 70b", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "Steelskull 2/L3.3-Cu-Mai-R1-70b": { - id: "Steelskull 2/L3.3-Cu-Mai-R1-70b", - name: "Llama 3.3 70B Cu Mai", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "Steelskull 2/L3.3-Nevoria-R1-70b": { - id: "Steelskull 2/L3.3-Nevoria-R1-70b", - name: "Steelskull Nevoria R1 70b", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "Steelskull 2/L3.3-MS-Nevoria-70b": { - id: "Steelskull 2/L3.3-MS-Nevoria-70b", - name: "Steelskull Nevoria 70b", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "Steelskull 2/L3.3-MS-Evayale-70B": { - id: "Steelskull 2/L3.3-MS-Evayale-70B", - name: "Evayale 70b ", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "Doctor-Shotgun 2/MS3.2-24B-Magnum-Diamond": { - id: "Doctor-Shotgun 2/MS3.2-24B-Magnum-Diamond", - name: "MS3.2 24B Magnum Diamond", - family: "mistral", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-11-24", - last_updated: "2025-11-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 32768 }, - }, - "shisa-ai/shisa-v2.1-llama3.3-70b": { - id: "shisa-ai/shisa-v2.1-llama3.3-70b", - name: "Shisa V2.1 Llama 3.3 70B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 0.5 }, - limit: { context: 32768, input: 32768, output: 4096 }, - }, - "shisa-ai/shisa-v2-llama3.3-70b": { - id: "shisa-ai/shisa-v2-llama3.3-70b", - name: "Shisa V2 Llama 3.3 70B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-07-26", - last_updated: "2025-07-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 0.5 }, - limit: { context: 128000, input: 128000, output: 16384 }, - }, - "anthropic/claude-sonnet-4.6:thinking": { - id: "anthropic/claude-sonnet-4.6:thinking", - name: "Claude Sonnet 4.6 Thinking", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.993999999999998 }, - limit: { context: 1000000, input: 1000000, output: 128000 }, - }, - "anthropic/claude-opus-4.6:thinking:low": { - id: "anthropic/claude-opus-4.6:thinking:low", - name: "Claude 4.6 Opus Thinking Low", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 4.998, output: 25.007 }, - limit: { context: 1000000, input: 1000000, output: 128000 }, - }, - "anthropic/claude-opus-4.6:thinking": { - id: "anthropic/claude-opus-4.6:thinking", - name: "Claude 4.6 Opus Thinking", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 4.998, output: 25.007 }, - limit: { context: 1000000, input: 1000000, output: 128000 }, - }, - "anthropic/claude-sonnet-4.6": { - id: "anthropic/claude-sonnet-4.6", - name: "Claude Sonnet 4.6", - family: "claude-sonnet", - attachment: true, - reasoning: false, - tool_call: true, - structured_output: true, - release_date: "2026-02-17", - last_updated: "2026-02-17", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.992, output: 14.993999999999998 }, - limit: { context: 1000000, input: 1000000, output: 128000 }, - }, - "anthropic/claude-opus-4.6:thinking:medium": { - id: "anthropic/claude-opus-4.6:thinking:medium", - name: "Claude 4.6 Opus Thinking Medium", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 4.998, output: 25.007 }, - limit: { context: 1000000, input: 1000000, output: 128000 }, - }, - "anthropic/claude-opus-4.6:thinking:max": { - id: "anthropic/claude-opus-4.6:thinking:max", - name: "Claude 4.6 Opus Thinking Max", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 4.998, output: 25.007 }, - limit: { context: 1000000, input: 1000000, output: 128000 }, - }, - "anthropic/claude-opus-4.6": { - id: "anthropic/claude-opus-4.6", - name: "Claude 4.6 Opus", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 4.998, output: 25.007 }, - limit: { context: 1000000, input: 1000000, output: 128000 }, - }, - "miromind-ai/mirothinker-v1.5-235b": { - id: "miromind-ai/mirothinker-v1.5-235b", - name: "MiroThinker v1.5 235B", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2026-01-07", - last_updated: "2026-01-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 32768, input: 32768, output: 4000 }, - }, - "huihui-ai/DeepSeek-R1-Distill-Llama-70B-abliterated": { - id: "huihui-ai/DeepSeek-R1-Distill-Llama-70B-abliterated", - name: "DeepSeek R1 Llama 70B Abliterated", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7, output: 0.7 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "huihui-ai/Qwen2.5-32B-Instruct-abliterated": { - id: "huihui-ai/Qwen2.5-32B-Instruct-abliterated", - name: "Qwen 2.5 32B Abliterated", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-01-06", - last_updated: "2025-01-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7, output: 0.7 }, - limit: { context: 32768, input: 32768, output: 8192 }, - }, - "huihui-ai/DeepSeek-R1-Distill-Qwen-32B-abliterated": { - id: "huihui-ai/DeepSeek-R1-Distill-Qwen-32B-abliterated", - name: "DeepSeek R1 Qwen Abliterated", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: false, - structured_output: false, - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.4, output: 1.4 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - "huihui-ai/Llama-3.3-70B-Instruct-abliterated": { - id: "huihui-ai/Llama-3.3-70B-Instruct-abliterated", - name: "Llama 3.3 70B Instruct abliterated", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-08-08", - last_updated: "2025-08-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7, output: 0.7 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "huihui-ai/Llama-3.1-Nemotron-70B-Instruct-HF-abliterated": { - id: "huihui-ai/Llama-3.1-Nemotron-70B-Instruct-HF-abliterated", - name: "Nemotron 3.1 70B abliterated", - family: "nemotron", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.7, output: 0.7 }, - limit: { context: 16384, input: 16384, output: 16384 }, - }, - "inflection/inflection-3-productivity": { - id: "inflection/inflection-3-productivity", - name: "Inflection 3 Productivity", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-10-11", - last_updated: "2024-10-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.499, output: 9.996 }, - limit: { context: 8000, input: 8000, output: 4096 }, - }, - "inflection/inflection-3-pi": { - id: "inflection/inflection-3-pi", - name: "Inflection 3 Pi", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-10-11", - last_updated: "2024-10-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2.499, output: 9.996 }, - limit: { context: 8000, input: 8000, output: 4096 }, - }, - "dmind/dmind-1-mini": { - id: "dmind/dmind-1-mini", - name: "DMind-1-Mini", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-06-01", - last_updated: "2025-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.4 }, - limit: { context: 32768, input: 32768, output: 8192 }, - }, - "dmind/dmind-1": { - id: "dmind/dmind-1", - name: "DMind-1", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2025-06-01", - last_updated: "2025-06-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.6 }, - limit: { context: 32768, input: 32768, output: 8192 }, - }, - "VongolaChouko 2/Starcannon-Unleashed-12B-v1.0": { - id: "VongolaChouko 2/Starcannon-Unleashed-12B-v1.0", - name: "Mistral Nemo Starcannon 12b v1", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - release_date: "2024-07-01", - last_updated: "2024-07-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.49299999999999994, output: 0.49299999999999994 }, - limit: { context: 16384, input: 16384, output: 8192 }, - }, - }, - }, - cerebras: { - id: "cerebras", - env: ["CEREBRAS_API_KEY"], - npm: "@ai-sdk/cerebras", - name: "Cerebras", - doc: "https://inference-docs.cerebras.ai/models/overview", - models: { - "qwen-3-235b-a22b-instruct-2507": { - id: "qwen-3-235b-a22b-instruct-2507", - name: "Qwen 3 235B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-22", - last_updated: "2025-07-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 1.2 }, - limit: { context: 131000, output: 32000 }, - }, - "gpt-oss-120b": { - id: "gpt-oss-120b", - name: "GPT OSS 120B", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 0.69 }, - limit: { context: 131072, output: 32768 }, - }, - "llama3.1-8b": { - id: "llama3.1-8b", - name: "Llama 3.1 8B", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.1 }, - limit: { context: 32000, output: 8000 }, - }, - "zai-glm-4.7": { - id: "zai-glm-4.7", - name: "Z.AI GLM-4.7", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2026-01-10", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.25, output: 2.75, cache_read: 0, cache_write: 0 }, - limit: { context: 131072, output: 40000 }, - }, - }, - }, - azure: { - id: "azure", - env: ["AZURE_RESOURCE_NAME", "AZURE_API_KEY"], - npm: "@ai-sdk/azure", - name: "Azure", - doc: "https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models", - models: { - "gpt-5.3-codex": { - id: "gpt-5.3-codex", - name: "GPT-5.3 Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-02-24", - last_updated: "2026-02-24", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-5-codex": { - id: "gpt-5-codex", - name: "GPT-5-Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-5-pro": { - id: "gpt-5-pro", - name: "GPT-5 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-10-06", - last_updated: "2025-10-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 120 }, - limit: { context: 400000, output: 272000 }, - }, - "phi-3-small-128k-instruct": { - id: "phi-3-small-128k-instruct", - name: "Phi-3-small-instruct (128k)", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 4096 }, - }, - "gpt-4o-mini": { - id: "gpt-4o-mini", - name: "GPT-4o mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.15, output: 0.6, cache_read: 0.08 }, - limit: { context: 128000, output: 16384 }, - }, - "text-embedding-ada-002": { - id: "text-embedding-ada-002", - name: "text-embedding-ada-002", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2022-12-15", - last_updated: "2022-12-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0 }, - limit: { context: 8192, output: 1536 }, - }, - "grok-4-fast-reasoning": { - id: "grok-4-fast-reasoning", - name: "Grok 4 Fast (Reasoning)", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-09-19", - last_updated: "2025-09-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "gpt-5.1-codex-max": { - id: "gpt-5.1-codex-max", - name: "GPT-5.1 Codex Max", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-13", - last_updated: "2025-11-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "phi-3-medium-128k-instruct": { - id: "phi-3-medium-128k-instruct", - name: "Phi-3-medium-instruct (128k)", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.17, output: 0.68 }, - limit: { context: 128000, output: 4096 }, - }, - "phi-4-multimodal": { - id: "phi-4-multimodal", - name: "Phi-4-multimodal", - family: "phi", - attachment: true, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text", "image", "audio"], output: ["text"] }, - open_weights: true, - cost: { input: 0.08, output: 0.32, input_audio: 4 }, - limit: { context: 128000, output: 4096 }, - }, - "mai-ds-r1": { - id: "mai-ds-r1", - name: "MAI-DS-R1", - family: "mai", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-06", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.35, output: 5.4 }, - limit: { context: 128000, output: 8192 }, - }, - "claude-opus-4-1": { - id: "claude-opus-4-1", - name: "Claude Opus 4.1", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 }, - limit: { context: 200000, output: 32000 }, - provider: { - npm: "@ai-sdk/anthropic", - api: "https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1", - }, - }, - "phi-3.5-moe-instruct": { - id: "phi-3.5-moe-instruct", - name: "Phi-3.5-MoE-instruct", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-08-20", - last_updated: "2024-08-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.16, output: 0.64 }, - limit: { context: 128000, output: 4096 }, - }, - "gpt-4-turbo-vision": { - id: "gpt-4-turbo-vision", - name: "GPT-4 Turbo Vision", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-11", - release_date: "2023-11-06", - last_updated: "2024-04-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 30 }, - limit: { context: 128000, output: 4096 }, - }, - "ministral-3b": { - id: "ministral-3b", - name: "Ministral 3B", - family: "ministral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2024-10-22", - last_updated: "2024-10-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.04 }, - limit: { context: 128000, output: 8192 }, - }, - "gpt-5.2-codex": { - id: "gpt-5.2-codex", - name: "GPT-5.2 Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-01-14", - last_updated: "2026-01-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 400000, output: 128000 }, - }, - "grok-3": { - id: "grok-3", - name: "Grok 3", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75 }, - limit: { context: 131072, output: 8192 }, - }, - "claude-opus-4-6": { - id: "claude-opus-4-6", - name: "Claude Opus 4.6", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { - input: 5, - output: 25, - cache_read: 0.5, - cache_write: 6.25, - context_over_200k: { input: 10, output: 37.5, cache_read: 1, cache_write: 12.5 }, - }, - limit: { context: 200000, output: 128000 }, - provider: { - npm: "@ai-sdk/anthropic", - api: "https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1", - }, - }, - "llama-3.2-90b-vision-instruct": { - id: "llama-3.2-90b-vision-instruct", - name: "Llama-3.2-90B-Vision-Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 2.04, output: 2.04 }, - limit: { context: 128000, output: 8192 }, - }, - "grok-code-fast-1": { - id: "grok-code-fast-1", - name: "Grok Code Fast 1", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2025-08-28", - last_updated: "2025-08-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.5, cache_read: 0.02 }, - limit: { context: 256000, output: 10000 }, - }, - "llama-3.3-70b-instruct": { - id: "llama-3.3-70b-instruct", - name: "Llama-3.3-70B-Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-12-06", - last_updated: "2024-12-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.71, output: 0.71 }, - limit: { context: 128000, output: 32768 }, - }, - "grok-4-1-fast-reasoning": { - id: "grok-4-1-fast-reasoning", - name: "Grok 4.1 Fast (Reasoning)", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-06-27", - last_updated: "2025-06-27", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 128000, input: 128000, output: 8192 }, - status: "beta", - }, - "phi-3.5-mini-instruct": { - id: "phi-3.5-mini-instruct", - name: "Phi-3.5-mini-instruct", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-08-20", - last_updated: "2024-08-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.52 }, - limit: { context: 128000, output: 4096 }, - }, - "cohere-command-a": { - id: "cohere-command-a", - name: "Command A", - family: "command-a", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06-01", - release_date: "2025-03-13", - last_updated: "2025-03-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.5, output: 10 }, - limit: { context: 256000, output: 8000 }, - }, - "mistral-medium-2505": { - id: "mistral-medium-2505", - name: "Mistral Medium 3", - family: "mistral-medium", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-05", - release_date: "2025-05-07", - last_updated: "2025-05-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 2 }, - limit: { context: 128000, output: 128000 }, - }, - "deepseek-v3.1": { - id: "deepseek-v3.1", - name: "DeepSeek-V3.1", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-08-21", - last_updated: "2025-08-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.56, output: 1.68 }, - limit: { context: 131072, output: 131072 }, - }, - "grok-4-1-fast-non-reasoning": { - id: "grok-4-1-fast-non-reasoning", - name: "Grok 4.1 Fast (Non-Reasoning)", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-06-27", - last_updated: "2025-06-27", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 128000, input: 128000, output: 8192 }, - status: "beta", - }, - o1: { - id: "o1", - name: "o1", - family: "o", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2023-09", - release_date: "2024-12-05", - last_updated: "2024-12-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 15, output: 60, cache_read: 7.5 }, - limit: { context: 200000, output: 100000 }, - }, - "gpt-5.1": { - id: "gpt-5.1", - name: "GPT-5.1", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text", "image", "audio"], output: ["text", "image", "audio"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 272000, output: 128000 }, - }, - "llama-4-scout-17b-16e-instruct": { - id: "llama-4-scout-17b-16e-instruct", - name: "Llama 4 Scout 17B 16E Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.78 }, - limit: { context: 128000, output: 8192 }, - }, - "meta-llama-3.1-405b-instruct": { - id: "meta-llama-3.1-405b-instruct", - name: "Meta-Llama-3.1-405B-Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 5.33, output: 16 }, - limit: { context: 128000, output: 32768 }, - }, - "cohere-command-r-plus-08-2024": { - id: "cohere-command-r-plus-08-2024", - name: "Command R+", - family: "command-r", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06-01", - release_date: "2024-08-30", - last_updated: "2024-08-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.5, output: 10 }, - limit: { context: 128000, output: 4000 }, - }, - "gpt-5.2-chat": { - id: "gpt-5.2-chat", - name: "GPT-5.2 Chat", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 128000, output: 16384 }, - }, - "gpt-5-chat": { - id: "gpt-5-chat", - name: "GPT-5 Chat", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: false, - temperature: false, - knowledge: "2024-10-24", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 128000, output: 16384 }, - }, - "grok-4": { - id: "grok-4", - name: "Grok 4", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, reasoning: 15, cache_read: 0.75 }, - limit: { context: 256000, output: 64000 }, - }, - "gpt-5.1-chat": { - id: "gpt-5.1-chat", - name: "GPT-5.1 Chat", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text", "image", "audio"], output: ["text", "image", "audio"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 128000, output: 16384 }, - }, - "meta-llama-3-8b-instruct": { - id: "meta-llama-3-8b-instruct", - name: "Meta-Llama-3-8B-Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-12", - release_date: "2024-04-18", - last_updated: "2024-04-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.61 }, - limit: { context: 8192, output: 2048 }, - }, - o3: { - id: "o3", - name: "o3", - family: "o", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 200000, output: 100000 }, - }, - "llama-3.2-11b-vision-instruct": { - id: "llama-3.2-11b-vision-instruct", - name: "Llama-3.2-11B-Vision-Instruct", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-09-25", - last_updated: "2024-09-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.37, output: 0.37 }, - limit: { context: 128000, output: 8192 }, - }, - "meta-llama-3-70b-instruct": { - id: "meta-llama-3-70b-instruct", - name: "Meta-Llama-3-70B-Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-12", - release_date: "2024-04-18", - last_updated: "2024-04-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.68, output: 3.54 }, - limit: { context: 8192, output: 2048 }, - }, - "deepseek-r1-0528": { - id: "deepseek-r1-0528", - name: "DeepSeek-R1-0528", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.35, output: 5.4 }, - limit: { context: 163840, output: 163840 }, - }, - "gpt-3.5-turbo-0301": { - id: "gpt-3.5-turbo-0301", - name: "GPT-3.5 Turbo 0301", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2021-08", - release_date: "2023-03-01", - last_updated: "2023-03-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 2 }, - limit: { context: 4096, output: 4096 }, - }, - "text-embedding-3-small": { - id: "text-embedding-3-small", - name: "text-embedding-3-small", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2024-01-25", - last_updated: "2024-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.02, output: 0 }, - limit: { context: 8191, output: 1536 }, - }, - "deepseek-r1": { - id: "deepseek-r1", - name: "DeepSeek-R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-07", - release_date: "2025-01-20", - last_updated: "2025-01-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.35, output: 5.4 }, - limit: { context: 163840, output: 163840 }, - }, - "phi-4-mini": { - id: "phi-4-mini", - name: "Phi-4-mini", - family: "phi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 128000, output: 4096 }, - }, - "deepseek-v3.2-speciale": { - id: "deepseek-v3.2-speciale", - name: "DeepSeek-V3.2-Speciale", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.58, output: 1.68 }, - limit: { context: 128000, output: 128000 }, - }, - "gpt-4.1-nano": { - id: "gpt-4.1-nano", - name: "GPT-4.1 nano", - family: "gpt-nano", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-05", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.4, cache_read: 0.03 }, - limit: { context: 1047576, output: 32768 }, - }, - "cohere-command-r-08-2024": { - id: "cohere-command-r-08-2024", - name: "Command R", - family: "command-r", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-06-01", - release_date: "2024-08-30", - last_updated: "2024-08-30", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 128000, output: 4000 }, - }, - "gpt-3.5-turbo-0613": { - id: "gpt-3.5-turbo-0613", - name: "GPT-3.5 Turbo 0613", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2021-08", - release_date: "2023-06-13", - last_updated: "2023-06-13", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 4 }, - limit: { context: 16384, output: 16384 }, - }, - "text-embedding-3-large": { - id: "text-embedding-3-large", - name: "text-embedding-3-large", - family: "text-embedding", - attachment: false, - reasoning: false, - tool_call: false, - release_date: "2024-01-25", - last_updated: "2024-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.13, output: 0 }, - limit: { context: 8191, output: 3072 }, - }, - "gpt-5.1-codex-mini": { - id: "gpt-5.1-codex-mini", - name: "GPT-5.1 Codex Mini", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.025 }, - limit: { context: 400000, output: 128000 }, - }, - "gpt-5.2": { - id: "gpt-5.2", - name: "GPT-5.2", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2025-12-11", - last_updated: "2025-12-11", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi K2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - structured_output: true, - temperature: true, - knowledge: "2025-01", - release_date: "2026-02-06", - last_updated: "2026-02-06", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3 }, - limit: { context: 262144, output: 262144 }, - provider: { - npm: "@ai-sdk/openai-compatible", - api: "https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/models", - shape: "completions", - }, - }, - "deepseek-v3-0324": { - id: "deepseek-v3-0324", - name: "DeepSeek-V3-0324", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-03-24", - last_updated: "2025-03-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1.14, output: 4.56 }, - limit: { context: 131072, output: 131072 }, - }, - "model-router": { - id: "model-router", - name: "Model Router", - family: "model-router", - attachment: true, - reasoning: false, - tool_call: true, - release_date: "2025-05-19", - last_updated: "2025-11-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.14, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - "gpt-4.1": { - id: "gpt-4.1", - name: "GPT-4.1", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-05", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 8, cache_read: 0.5 }, - limit: { context: 1047576, output: 32768 }, - }, - "gpt-4-turbo": { - id: "gpt-4-turbo", - name: "GPT-4 Turbo", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-11", - release_date: "2023-11-06", - last_updated: "2024-04-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 10, output: 30 }, - limit: { context: 128000, output: 4096 }, - }, - "mistral-nemo": { - id: "mistral-nemo", - name: "Mistral Nemo", - family: "mistral-nemo", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2024-07-18", - last_updated: "2024-07-18", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.15 }, - limit: { context: 128000, output: 128000 }, - }, - "deepseek-v3.2": { - id: "deepseek-v3.2", - name: "DeepSeek-V3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-12-01", - last_updated: "2025-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.58, output: 1.68 }, - limit: { context: 128000, output: 128000 }, - }, - "cohere-embed-v-4-0": { - id: "cohere-embed-v-4-0", - name: "Embed v4", - family: "cohere-embed", - attachment: true, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2025-04-15", - last_updated: "2025-04-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.12, output: 0 }, - limit: { context: 128000, output: 1536 }, - }, - "grok-3-mini": { - id: "grok-3-mini", - name: "Grok 3 Mini", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.5, reasoning: 0.5, cache_read: 0.075 }, - limit: { context: 131072, output: 8192 }, - }, - "gpt-4-32k": { - id: "gpt-4-32k", - name: "GPT-4 32K", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-11", - release_date: "2023-03-14", - last_updated: "2023-03-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 60, output: 120 }, - limit: { context: 32768, output: 32768 }, - }, - "gpt-5": { - id: "gpt-5", - name: "GPT-5", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.13 }, - limit: { context: 272000, output: 128000 }, - }, - "o4-mini": { - id: "o4-mini", - name: "o4-mini", - family: "o-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05", - release_date: "2025-04-16", - last_updated: "2025-04-16", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.28 }, - limit: { context: 200000, output: 100000 }, - }, - "phi-4": { - id: "phi-4", - name: "Phi-4", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.125, output: 0.5 }, - limit: { context: 128000, output: 4096 }, - }, - "gpt-4.1-mini": { - id: "gpt-4.1-mini", - name: "GPT-4.1 mini", - family: "gpt-mini", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-05", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.4, output: 1.6, cache_read: 0.1 }, - limit: { context: 1047576, output: 32768 }, - }, - "phi-4-reasoning-plus": { - id: "phi-4-reasoning-plus", - name: "Phi-4-reasoning-plus", - family: "phi", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.125, output: 0.5 }, - limit: { context: 32000, output: 4096 }, - }, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "Kimi K2 Thinking", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-11-06", - last_updated: "2025-12-02", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 2.5, cache_read: 0.15 }, - limit: { context: 262144, output: 262144 }, - }, - "gpt-5.4": { - id: "gpt-5.4", - name: "GPT-5.4", - family: "gpt", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 15, cache_read: 0.25 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "codex-mini": { - id: "codex-mini", - name: "Codex Mini", - family: "gpt-codex-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-04", - release_date: "2025-05-16", - last_updated: "2025-05-16", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 6, cache_read: 0.375 }, - limit: { context: 200000, output: 100000 }, - }, - "phi-3-mini-4k-instruct": { - id: "phi-3-mini-4k-instruct", - name: "Phi-3-mini-instruct (4k)", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.52 }, - limit: { context: 4096, output: 1024 }, - }, - "meta-llama-3.1-70b-instruct": { - id: "meta-llama-3.1-70b-instruct", - name: "Meta-Llama-3.1-70B-Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 2.68, output: 3.54 }, - limit: { context: 128000, output: 32768 }, - }, - "o1-preview": { - id: "o1-preview", - name: "o1-preview", - family: "o", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2023-09", - release_date: "2024-09-12", - last_updated: "2024-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 16.5, output: 66, cache_read: 8.25 }, - limit: { context: 128000, output: 32768 }, - }, - "gpt-5.4-pro": { - id: "gpt-5.4-pro", - name: "GPT-5.4 Pro", - family: "gpt-pro", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-05", - last_updated: "2026-03-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 30, output: 180 }, - limit: { context: 400000, input: 272000, output: 128000 }, - }, - "gpt-5.3-chat": { - id: "gpt-5.3-chat", - name: "GPT-5.3 Chat", - family: "gpt-codex", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2025-08-31", - release_date: "2026-03-03", - last_updated: "2026-03-03", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.75, output: 14, cache_read: 0.175 }, - limit: { context: 128000, output: 16384 }, - }, - "meta-llama-3.1-8b-instruct": { - id: "meta-llama-3.1-8b-instruct", - name: "Meta-Llama-3.1-8B-Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.61 }, - limit: { context: 128000, output: 32768 }, - }, - "claude-haiku-4-5": { - id: "claude-haiku-4-5", - name: "Claude Haiku 4.5", - family: "claude-haiku", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-02-31", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, - limit: { context: 200000, output: 64000 }, - provider: { - npm: "@ai-sdk/anthropic", - api: "https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1", - }, - }, - "gpt-5.1-codex": { - id: "gpt-5.1-codex", - name: "GPT-5.1 Codex", - family: "gpt-codex", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: false, - knowledge: "2024-09-30", - release_date: "2025-11-14", - last_updated: "2025-11-14", - modalities: { input: ["text", "image", "audio"], output: ["text", "image", "audio"] }, - open_weights: false, - cost: { input: 1.25, output: 10, cache_read: 0.125 }, - limit: { context: 400000, output: 128000 }, - }, - "mistral-large-2411": { - id: "mistral-large-2411", - name: "Mistral Large 24.11", - family: "mistral-large", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2024-11-01", - last_updated: "2024-11-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6 }, - limit: { context: 128000, output: 32768 }, - }, - "claude-opus-4-5": { - id: "claude-opus-4-5", - name: "Claude Opus 4.5", - family: "claude-opus", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-03-31", - release_date: "2025-11-24", - last_updated: "2025-08-01", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 }, - limit: { context: 200000, output: 64000 }, - provider: { - npm: "@ai-sdk/anthropic", - api: "https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1", - }, - }, - "phi-4-mini-reasoning": { - id: "phi-4-mini-reasoning", - name: "Phi-4-mini-reasoning", - family: "phi", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.075, output: 0.3 }, - limit: { context: 128000, output: 4096 }, - }, - "gpt-3.5-turbo-0125": { - id: "gpt-3.5-turbo-0125", - name: "GPT-3.5 Turbo 0125", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2021-08", - release_date: "2024-01-25", - last_updated: "2024-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 1.5 }, - limit: { context: 16384, output: 16384 }, - }, - "cohere-embed-v3-multilingual": { - id: "cohere-embed-v3-multilingual", - name: "Embed v3 Multilingual", - family: "cohere-embed", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2023-11-07", - last_updated: "2023-11-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0 }, - limit: { context: 512, output: 1024 }, - }, - "phi-3-medium-4k-instruct": { - id: "phi-3-medium-4k-instruct", - name: "Phi-3-medium-instruct (4k)", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.17, output: 0.68 }, - limit: { context: 4096, output: 1024 }, - }, - "cohere-embed-v3-english": { - id: "cohere-embed-v3-english", - name: "Embed v3 English", - family: "cohere-embed", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - release_date: "2023-11-07", - last_updated: "2023-11-07", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0 }, - limit: { context: 512, output: 1024 }, - }, - "o3-mini": { - id: "o3-mini", - name: "o3-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05", - release_date: "2024-12-20", - last_updated: "2025-01-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.55 }, - limit: { context: 200000, output: 100000 }, - }, - "grok-4-fast-non-reasoning": { - id: "grok-4-fast-non-reasoning", - name: "Grok 4 Fast (Non-Reasoning)", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-09-19", - last_updated: "2025-09-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "llama-4-maverick-17b-128e-instruct-fp8": { - id: "llama-4-maverick-17b-128e-instruct-fp8", - name: "Llama 4 Maverick 17B 128E Instruct FP8", - family: "llama", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2025-04-05", - last_updated: "2025-04-05", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 1 }, - limit: { context: 128000, output: 8192 }, - }, - "claude-sonnet-4-5": { - id: "claude-sonnet-4-5", - name: "Claude Sonnet 4.5", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-11-18", - last_updated: "2025-11-18", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 }, - limit: { context: 200000, output: 64000 }, - provider: { - npm: "@ai-sdk/anthropic", - api: "https://${AZURE_RESOURCE_NAME}.services.ai.azure.com/anthropic/v1", - }, - }, - "gpt-5-mini": { - id: "gpt-5-mini", - name: "GPT-5 Mini", - family: "gpt-mini", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 2, cache_read: 0.03 }, - limit: { context: 272000, output: 128000 }, - }, - "phi-3-mini-128k-instruct": { - id: "phi-3-mini-128k-instruct", - name: "Phi-3-mini-instruct (128k)", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.52 }, - limit: { context: 128000, output: 4096 }, - }, - "phi-4-reasoning": { - id: "phi-4-reasoning", - name: "Phi-4-reasoning", - family: "phi", - attachment: false, - reasoning: true, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-12-11", - last_updated: "2024-12-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.125, output: 0.5 }, - limit: { context: 32000, output: 4096 }, - }, - "gpt-3.5-turbo-1106": { - id: "gpt-3.5-turbo-1106", - name: "GPT-3.5 Turbo 1106", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2021-08", - release_date: "2023-11-06", - last_updated: "2023-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1, output: 2 }, - limit: { context: 16384, output: 16384 }, - }, - "gpt-4": { - id: "gpt-4", - name: "GPT-4", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-11", - release_date: "2023-03-14", - last_updated: "2023-03-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 60, output: 120 }, - limit: { context: 8192, output: 8192 }, - }, - "gpt-5-nano": { - id: "gpt-5-nano", - name: "GPT-5 Nano", - family: "gpt-nano", - attachment: true, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2024-05-30", - release_date: "2025-08-07", - last_updated: "2025-08-07", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.05, output: 0.4, cache_read: 0.01 }, - limit: { context: 272000, output: 128000 }, - }, - "gpt-3.5-turbo-instruct": { - id: "gpt-3.5-turbo-instruct", - name: "GPT-3.5 Turbo Instruct", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2021-08", - release_date: "2023-09-21", - last_updated: "2023-09-21", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.5, output: 2 }, - limit: { context: 4096, output: 4096 }, - }, - "o1-mini": { - id: "o1-mini", - name: "o1-mini", - family: "o-mini", - attachment: false, - reasoning: true, - tool_call: true, - temperature: false, - knowledge: "2023-09", - release_date: "2024-09-12", - last_updated: "2024-09-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 1.1, output: 4.4, cache_read: 0.55 }, - limit: { context: 128000, output: 65536 }, - }, - "mistral-small-2503": { - id: "mistral-small-2503", - name: "Mistral Small 3.1", - family: "mistral-small", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-09", - release_date: "2025-03-01", - last_updated: "2025-03-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.1, output: 0.3 }, - limit: { context: 128000, output: 32768 }, - }, - "codestral-2501": { - id: "codestral-2501", - name: "Codestral 25.01", - family: "codestral", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-03", - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 256000, output: 256000 }, - }, - "gpt-4o": { - id: "gpt-4o", - name: "GPT-4o", - family: "gpt", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-09", - release_date: "2024-05-13", - last_updated: "2024-05-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.5, output: 10, cache_read: 1.25 }, - limit: { context: 128000, output: 16384 }, - }, - "phi-3-small-8k-instruct": { - id: "phi-3-small-8k-instruct", - name: "Phi-3-small-instruct (8k)", - family: "phi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2023-10", - release_date: "2024-04-23", - last_updated: "2024-04-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 8192, output: 2048 }, - }, - }, - }, - cortecs: { - id: "cortecs", - env: ["CORTECS_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://api.cortecs.ai/v1", - name: "Cortecs", - doc: "https://api.cortecs.ai/v1/models", - models: { - "kimi-k2-instruct": { - id: "kimi-k2-instruct", - name: "Kimi K2 Instruct", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-07-11", - last_updated: "2025-09-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.551, output: 2.646 }, - limit: { context: 131000, output: 131000 }, - }, - "qwen3-next-80b-a3b-thinking": { - id: "qwen3-next-80b-a3b-thinking", - name: "Qwen3 Next 80B A3B Thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-11", - last_updated: "2025-09-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.164, output: 1.311 }, - limit: { context: 128000, output: 128000 }, - }, - "qwen3-coder-480b-a35b-instruct": { - id: "qwen3-coder-480b-a35b-instruct", - name: "Qwen3 Coder 480B A35B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-07-25", - last_updated: "2025-07-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.441, output: 1.984 }, - limit: { context: 262000, output: 262000 }, - }, - "glm-4.5-air": { - id: "glm-4.5-air", - name: "GLM 4.5 Air", - family: "glm-air", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-08-01", - last_updated: "2025-08-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.22, output: 1.34 }, - limit: { context: 131072, output: 131072 }, - }, - "glm-4.5": { - id: "glm-4.5", - name: "GLM 4.5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-29", - last_updated: "2025-07-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.67, output: 2.46 }, - limit: { context: 131072, output: 131072 }, - }, - "glm-4.7-flash": { - id: "glm-4.7-flash", - name: "GLM-4.7-Flash", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-08-08", - last_updated: "2025-08-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.53 }, - limit: { context: 203000, output: 203000 }, - }, - "qwen3-32b": { - id: "qwen3-32b", - name: "Qwen3 32B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-12", - release_date: "2025-04-29", - last_updated: "2025-04-29", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.099, output: 0.33 }, - limit: { context: 16384, output: 16384 }, - }, - "minimax-m2.1": { - id: "minimax-m2.1", - name: "MiniMax-M2.1", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2025-12-23", - last_updated: "2025-12-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.34, output: 1.34 }, - limit: { context: 196000, output: 196000 }, - }, - "devstral-small-2512": { - id: "devstral-small-2512", - name: "Devstral Small 2 2512", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-12", - release_date: "2025-12-09", - last_updated: "2025-12-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262000, output: 262000 }, - }, - "intellect-3": { - id: "intellect-3", - name: "INTELLECT 3", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-11", - release_date: "2025-11-26", - last_updated: "2025-11-26", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.219, output: 1.202 }, - limit: { context: 128000, output: 128000 }, - }, - "nova-pro-v1": { - id: "nova-pro-v1", - name: "Nova Pro 1.0", - family: "nova-pro", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-12-03", - last_updated: "2024-12-03", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.016, output: 4.061 }, - limit: { context: 300000, output: 5000 }, - }, - "gpt-oss-120b": { - id: "gpt-oss-120b", - name: "GPT Oss 120b", - family: "gpt-oss", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-01", - release_date: "2025-08-05", - last_updated: "2025-08-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 128000 }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Kimi K2.5", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.55, output: 2.76 }, - limit: { context: 256000, output: 256000 }, - }, - "deepseek-v3-0324": { - id: "deepseek-v3-0324", - name: "DeepSeek V3 0324", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-07", - release_date: "2025-03-24", - last_updated: "2025-03-24", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.551, output: 1.654 }, - limit: { context: 128000, output: 128000 }, - }, - "gpt-4.1": { - id: "gpt-4.1", - name: "GPT 4.1", - family: "gpt", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-06", - release_date: "2025-04-14", - last_updated: "2025-04-14", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2.354, output: 9.417 }, - limit: { context: 1047576, output: 32768 }, - }, - "llama-3.1-405b-instruct": { - id: "llama-3.1-405b-instruct", - name: "Llama 3.1 405B Instruct", - family: "llama", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2023-12", - release_date: "2024-07-23", - last_updated: "2024-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 128000 }, - }, - "devstral-2512": { - id: "devstral-2512", - name: "Devstral 2 2512", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-12", - release_date: "2025-12-09", - last_updated: "2025-12-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0, output: 0 }, - limit: { context: 262000, output: 262000 }, - }, - "glm-4.7": { - id: "glm-4.7", - name: "GLM 4.7", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-04", - release_date: "2025-12-22", - last_updated: "2025-12-22", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.45, output: 2.23 }, - limit: { context: 198000, output: 198000 }, - }, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "Kimi K2 Thinking", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2025-12", - release_date: "2025-12-08", - last_updated: "2025-12-08", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.656, output: 2.731 }, - limit: { context: 262000, output: 262000 }, - }, - "minimax-m2": { - id: "minimax-m2", - name: "MiniMax-M2", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - knowledge: "2024-11", - release_date: "2025-10-27", - last_updated: "2025-10-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.39, output: 1.57 }, - limit: { context: 400000, output: 400000 }, - }, - "claude-sonnet-4": { - id: "claude-sonnet-4", - name: "Claude Sonnet 4", - family: "claude-sonnet", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-03", - release_date: "2025-05-22", - last_updated: "2025-05-22", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3.307, output: 16.536 }, - limit: { context: 200000, output: 64000 }, - }, - "claude-4-5-sonnet": { - id: "claude-4-5-sonnet", - name: "Claude 4.5 Sonnet", - family: "claude-sonnet", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07-31", - release_date: "2025-09-29", - last_updated: "2025-09-29", - modalities: { input: ["text", "image", "pdf"], output: ["text"] }, - open_weights: false, - cost: { input: 3.259, output: 16.296 }, - limit: { context: 200000, output: 200000 }, - }, - "gemini-2.5-pro": { - id: "gemini-2.5-pro", - name: "Gemini 2.5 Pro", - family: "gemini-pro", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-01", - release_date: "2025-03-20", - last_updated: "2025-06-17", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.654, output: 11.024 }, - limit: { context: 1048576, output: 65535 }, - }, - }, - }, - xai: { - id: "xai", - env: ["XAI_API_KEY"], - npm: "@ai-sdk/xai", - name: "xAI", - doc: "https://docs.x.ai/docs/models", - models: { - "grok-2-1212": { - id: "grok-2-1212", - name: "Grok 2 (1212)", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-12-12", - last_updated: "2024-12-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 10, cache_read: 2 }, - limit: { context: 131072, output: 8192 }, - }, - "grok-4.20-beta-latest-non-reasoning": { - id: "grok-4.20-beta-latest-non-reasoning", - name: "Grok 4.20 Beta (Non-Reasoning)", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2026-03-09", - last_updated: "2026-03-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6, cache_read: 0.2, context_over_200k: { input: 4, output: 12, cache_read: 0.4 } }, - limit: { context: 2000000, output: 30000 }, - status: "beta", - }, - "grok-2": { - id: "grok-2", - name: "Grok 2", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-08-20", - last_updated: "2024-08-20", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 10, cache_read: 2 }, - limit: { context: 131072, output: 8192 }, - }, - "grok-3-fast-latest": { - id: "grok-3-fast-latest", - name: "Grok 3 Fast Latest", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 1.25 }, - limit: { context: 131072, output: 8192 }, - }, - "grok-2-vision": { - id: "grok-2-vision", - name: "Grok 2 Vision", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-08-20", - last_updated: "2024-08-20", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 10, cache_read: 2 }, - limit: { context: 8192, output: 4096 }, - }, - "grok-3": { - id: "grok-3", - name: "Grok 3", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75 }, - limit: { context: 131072, output: 8192 }, - }, - "grok-code-fast-1": { - id: "grok-code-fast-1", - name: "Grok Code Fast 1", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2023-10", - release_date: "2025-08-28", - last_updated: "2025-08-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 1.5, cache_read: 0.02 }, - limit: { context: 256000, output: 10000 }, - }, - "grok-2-vision-1212": { - id: "grok-2-vision-1212", - name: "Grok 2 Vision (1212)", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-08-20", - last_updated: "2024-12-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 10, cache_read: 2 }, - limit: { context: 8192, output: 4096 }, - }, - "grok-4-1-fast-non-reasoning": { - id: "grok-4-1-fast-non-reasoning", - name: "Grok 4.1 Fast (Non-Reasoning)", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "grok-beta": { - id: "grok-beta", - name: "Grok Beta", - family: "grok-beta", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-11-01", - last_updated: "2024-11-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 15, cache_read: 5 }, - limit: { context: 131072, output: 4096 }, - }, - "grok-3-mini-fast": { - id: "grok-3-mini-fast", - name: "Grok 3 Mini Fast", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 4, reasoning: 4, cache_read: 0.15 }, - limit: { context: 131072, output: 8192 }, - }, - "grok-4-fast": { - id: "grok-4-fast", - name: "Grok 4 Fast", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-09-19", - last_updated: "2025-09-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "grok-4": { - id: "grok-4", - name: "Grok 4", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-07-09", - last_updated: "2025-07-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, reasoning: 15, cache_read: 0.75 }, - limit: { context: 256000, output: 64000 }, - }, - "grok-4.20-multi-agent-beta-latest": { - id: "grok-4.20-multi-agent-beta-latest", - name: "Grok 4.20 Multi-Agent Beta", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-09", - last_updated: "2026-03-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6, cache_read: 0.2, context_over_200k: { input: 4, output: 12, cache_read: 0.4 } }, - limit: { context: 2000000, output: 30000 }, - status: "beta", - }, - "grok-3-latest": { - id: "grok-3-latest", - name: "Grok 3 Latest", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 3, output: 15, cache_read: 0.75 }, - limit: { context: 131072, output: 8192 }, - }, - "grok-4-1-fast": { - id: "grok-4-1-fast", - name: "Grok 4.1 Fast", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-11-19", - last_updated: "2025-11-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "grok-2-vision-latest": { - id: "grok-2-vision-latest", - name: "Grok 2 Vision Latest", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-08-20", - last_updated: "2024-12-12", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 10, cache_read: 2 }, - limit: { context: 8192, output: 4096 }, - }, - "grok-3-mini-latest": { - id: "grok-3-mini-latest", - name: "Grok 3 Mini Latest", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.5, reasoning: 0.5, cache_read: 0.075 }, - limit: { context: 131072, output: 8192 }, - }, - "grok-3-mini": { - id: "grok-3-mini", - name: "Grok 3 Mini", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.3, output: 0.5, reasoning: 0.5, cache_read: 0.075 }, - limit: { context: 131072, output: 8192 }, - }, - "grok-3-mini-fast-latest": { - id: "grok-3-mini-fast-latest", - name: "Grok 3 Mini Fast Latest", - family: "grok", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.6, output: 4, reasoning: 4, cache_read: 0.15 }, - limit: { context: 131072, output: 8192 }, - }, - "grok-2-latest": { - id: "grok-2-latest", - name: "Grok 2 Latest", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-08-20", - last_updated: "2024-12-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 10, cache_read: 2 }, - limit: { context: 131072, output: 8192 }, - }, - "grok-4-fast-non-reasoning": { - id: "grok-4-fast-non-reasoning", - name: "Grok 4 Fast (Non-Reasoning)", - family: "grok", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-07", - release_date: "2025-09-19", - last_updated: "2025-09-19", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.2, output: 0.5, cache_read: 0.05 }, - limit: { context: 2000000, output: 30000 }, - }, - "grok-vision-beta": { - id: "grok-vision-beta", - name: "Grok Vision Beta", - family: "grok-vision", - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-08", - release_date: "2024-11-01", - last_updated: "2024-11-01", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 15, cache_read: 5 }, - limit: { context: 8192, output: 4096 }, - }, - "grok-3-fast": { - id: "grok-3-fast", - name: "Grok 3 Fast", - family: "grok", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-11", - release_date: "2025-02-17", - last_updated: "2025-02-17", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 5, output: 25, cache_read: 1.25 }, - limit: { context: 131072, output: 8192 }, - }, - "grok-4.20-beta-latest-reasoning": { - id: "grok-4.20-beta-latest-reasoning", - name: "Grok 4.20 Beta (Reasoning)", - family: "grok", - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-03-09", - last_updated: "2026-03-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 2, output: 6, cache_read: 0.2, context_over_200k: { input: 4, output: 12, cache_read: 0.4 } }, - limit: { context: 2000000, output: 30000 }, - status: "beta", - }, - }, - }, - "alibaba-cn": { - id: "alibaba-cn", - env: ["DASHSCOPE_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://dashscope.aliyuncs.com/compatible-mode/v1", - name: "Alibaba (China)", - doc: "https://www.alibabacloud.com/help/en/model-studio/models", - models: { - "qwen-vl-plus": { - id: "qwen-vl-plus", - name: "Qwen-VL Plus", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-01-25", - last_updated: "2025-08-15", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.115, output: 0.287 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen-vl-max": { - id: "qwen-vl-max", - name: "Qwen-VL Max", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-04-08", - last_updated: "2025-08-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.23, output: 0.574 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen-math-plus": { - id: "qwen-math-plus", - name: "Qwen Math Plus", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-08-16", - last_updated: "2024-09-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.574, output: 1.721 }, - limit: { context: 4096, output: 3072 }, - }, - "deepseek-v3-1": { - id: "deepseek-v3-1", - name: "DeepSeek V3.1", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.574, output: 1.721 }, - limit: { context: 131072, output: 65536 }, - }, - "glm-5": { - id: "glm-5", - name: "GLM-5", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-11", - last_updated: "2026-02-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.86, output: 3.15 }, - limit: { context: 202752, output: 16384 }, - }, - "qwen2-5-coder-7b-instruct": { - id: "qwen2-5-coder-7b-instruct", - name: "Qwen2.5-Coder 7B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-11", - last_updated: "2024-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.144, output: 0.287 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-next-80b-a3b-thinking": { - id: "qwen3-next-80b-a3b-thinking", - name: "Qwen3-Next 80B-A3B (Thinking)", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09", - last_updated: "2025-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.144, output: 1.434 }, - limit: { context: 131072, output: 32768 }, - }, - "deepseek-v3": { - id: "deepseek-v3", - name: "DeepSeek V3", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2024-12-01", - last_updated: "2024-12-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.287, output: 1.147 }, - limit: { context: 65536, output: 8192 }, - }, - "qwen3-coder-480b-a35b-instruct": { - id: "qwen3-coder-480b-a35b-instruct", - name: "Qwen3-Coder 480B-A35B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.861, output: 3.441 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen-long": { - id: "qwen-long", - name: "Qwen Long", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-01-25", - last_updated: "2025-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.072, output: 0.287 }, - limit: { context: 10000000, output: 8192 }, - }, - "qwen3-14b": { - id: "qwen3-14b", - name: "Qwen3 14B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.144, output: 0.574, reasoning: 1.434 }, - limit: { context: 131072, output: 8192 }, - }, - "qwq-32b": { - id: "qwq-32b", - name: "QwQ 32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-12", - last_updated: "2024-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.287, output: 0.861 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-coder-flash": { - id: "qwen3-coder-flash", - name: "Qwen3 Coder Flash", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.144, output: 0.574 }, - limit: { context: 1000000, output: 65536 }, - }, - "qwen3-vl-30b-a3b": { - id: "qwen3-vl-30b-a3b", - name: "Qwen3-VL 30B-A3B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.108, output: 0.431, reasoning: 1.076 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen3-asr-flash": { - id: "qwen3-asr-flash", - name: "Qwen3-ASR Flash", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: false, - knowledge: "2024-04", - release_date: "2025-09-08", - last_updated: "2025-09-08", - modalities: { input: ["audio"], output: ["text"] }, - open_weights: false, - cost: { input: 0.032, output: 0.032 }, - limit: { context: 53248, output: 4096 }, - }, - "qwen-max": { - id: "qwen-max", - name: "Qwen Max", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-04-03", - last_updated: "2025-01-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.345, output: 1.377 }, - limit: { context: 131072, output: 8192 }, - }, - "deepseek-r1-distill-qwen-14b": { - id: "deepseek-r1-distill-qwen-14b", - name: "DeepSeek R1 Distill Qwen 14B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.144, output: 0.431 }, - limit: { context: 32768, output: 16384 }, - }, - "moonshot-kimi-k2-instruct": { - id: "moonshot-kimi-k2-instruct", - name: "Moonshot Kimi K2 Instruct", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.574, output: 2.294 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen-doc-turbo": { - id: "qwen-doc-turbo", - name: "Qwen Doc Turbo", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-01", - last_updated: "2024-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.087, output: 0.144 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen-turbo": { - id: "qwen-turbo", - name: "Qwen Turbo", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-11-01", - last_updated: "2025-07-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.044, output: 0.087, reasoning: 0.431 }, - limit: { context: 1000000, output: 16384 }, - }, - "qwen2-5-7b-instruct": { - id: "qwen2-5-7b-instruct", - name: "Qwen2.5 7B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.072, output: 0.144 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen2-5-vl-72b-instruct": { - id: "qwen2-5-vl-72b-instruct", - name: "Qwen2.5-VL 72B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 2.294, output: 6.881 }, - limit: { context: 131072, output: 8192 }, - }, - "tongyi-intent-detect-v3": { - id: "tongyi-intent-detect-v3", - name: "Tongyi Intent Detect V3", - family: "yi", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-04", - release_date: "2024-01", - last_updated: "2024-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.058, output: 0.144 }, - limit: { context: 8192, output: 1024 }, - }, - "qwen2-5-14b-instruct": { - id: "qwen2-5-14b-instruct", - name: "Qwen2.5 14B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.144, output: 0.431 }, - limit: { context: 131072, output: 8192 }, - }, - "deepseek-r1-0528": { - id: "deepseek-r1-0528", - name: "DeepSeek R1 0528", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-05-28", - last_updated: "2025-05-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.574, output: 2.294 }, - limit: { context: 131072, output: 16384 }, - }, - "qwen3-8b": { - id: "qwen3-8b", - name: "Qwen3 8B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.072, output: 0.287, reasoning: 0.717 }, - limit: { context: 131072, output: 8192 }, - }, - "deepseek-r1": { - id: "deepseek-r1", - name: "DeepSeek R1", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.574, output: 2.294 }, - limit: { context: 131072, output: 16384 }, - }, - "qwen3-32b": { - id: "qwen3-32b", - name: "Qwen3 32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.287, output: 1.147, reasoning: 2.868 }, - limit: { context: 131072, output: 16384 }, - }, - "qwen3.5-397b-a17b": { - id: "qwen3.5-397b-a17b", - name: "Qwen3.5 397B-A17B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-16", - last_updated: "2026-02-16", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.43, output: 2.58, reasoning: 2.58 }, - limit: { context: 262144, output: 65536 }, - }, - "qvq-max": { - id: "qvq-max", - name: "QVQ Max", - family: "qvq", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-03-25", - last_updated: "2025-03-25", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 1.147, output: 4.588 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen2-5-omni-7b": { - id: "qwen2-5-omni-7b", - name: "Qwen2.5-Omni 7B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-12", - last_updated: "2024-12", - modalities: { input: ["text", "image", "audio", "video"], output: ["text", "audio"] }, - open_weights: true, - cost: { input: 0.087, output: 0.345, input_audio: 5.448 }, - limit: { context: 32768, output: 2048 }, - }, - "qwen-plus-character": { - id: "qwen-plus-character", - name: "Qwen Plus Character", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-01", - last_updated: "2024-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.115, output: 0.287 }, - limit: { context: 32768, output: 4096 }, - }, - "deepseek-r1-distill-llama-70b": { - id: "deepseek-r1-distill-llama-70b", - name: "DeepSeek R1 Distill Llama 70B", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.287, output: 0.861 }, - limit: { context: 32768, output: 16384 }, - }, - "qwen2-5-vl-7b-instruct": { - id: "qwen2-5-vl-7b-instruct", - name: "Qwen2.5-VL 7B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.287, output: 0.717 }, - limit: { context: 131072, output: 8192 }, - }, - "kimi-k2.5": { - id: "kimi-k2.5", - name: "Moonshot Kimi K2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: false, - temperature: true, - knowledge: "2025-01", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.574, output: 2.411 }, - limit: { context: 262144, output: 32768 }, - }, - "qwen-omni-turbo-realtime": { - id: "qwen-omni-turbo-realtime", - name: "Qwen-Omni Turbo Realtime", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-05-08", - last_updated: "2025-05-08", - modalities: { input: ["text", "image", "audio"], output: ["text", "audio"] }, - open_weights: false, - cost: { input: 0.23, output: 0.918, input_audio: 3.584, output_audio: 7.168 }, - limit: { context: 32768, output: 2048 }, - }, - "deepseek-v3-2-exp": { - id: "deepseek-v3-2-exp", - name: "DeepSeek V3.2 Exp", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.287, output: 0.431 }, - limit: { context: 131072, output: 65536 }, - }, - "deepseek-r1-distill-llama-8b": { - id: "deepseek-r1-distill-llama-8b", - name: "DeepSeek R1 Distill Llama 8B", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 16384 }, - }, - "qwen3-235b-a22b": { - id: "qwen3-235b-a22b", - name: "Qwen3 235B-A22B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.287, output: 1.147, reasoning: 2.868 }, - limit: { context: 131072, output: 16384 }, - }, - "qwen3-coder-30b-a3b-instruct": { - id: "qwen3-coder-30b-a3b-instruct", - name: "Qwen3-Coder 30B-A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.216, output: 0.861 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen-omni-turbo": { - id: "qwen-omni-turbo", - name: "Qwen-Omni Turbo", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-01-19", - last_updated: "2025-03-26", - modalities: { input: ["text", "image", "audio", "video"], output: ["text", "audio"] }, - open_weights: false, - cost: { input: 0.058, output: 0.23, input_audio: 3.584, output_audio: 7.168 }, - limit: { context: 32768, output: 2048 }, - }, - "qwen-mt-plus": { - id: "qwen-mt-plus", - name: "Qwen-MT Plus", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-04", - release_date: "2025-01", - last_updated: "2025-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.259, output: 0.775 }, - limit: { context: 16384, output: 8192 }, - }, - "qwen3.5-flash": { - id: "qwen3.5-flash", - name: "Qwen3.5 Flash", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-23", - last_updated: "2026-02-23", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.172, output: 1.72, reasoning: 1.72 }, - limit: { context: 1000000, output: 65536 }, - }, - "qwen2-5-math-7b-instruct": { - id: "qwen2-5-math-7b-instruct", - name: "Qwen2.5-Math 7B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.144, output: 0.287 }, - limit: { context: 4096, output: 3072 }, - }, - "deepseek-r1-distill-qwen-1-5b": { - id: "deepseek-r1-distill-qwen-1-5b", - name: "DeepSeek R1 Distill Qwen 1.5B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 32768, output: 16384 }, - }, - "deepseek-r1-distill-qwen-7b": { - id: "deepseek-r1-distill-qwen-7b", - name: "DeepSeek R1 Distill Qwen 7B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.072, output: 0.144 }, - limit: { context: 32768, output: 16384 }, - }, - "kimi-k2-thinking": { - id: "kimi-k2-thinking", - name: "Moonshot Kimi K2 Thinking", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-11-06", - last_updated: "2025-11-06", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.574, output: 2.294 }, - limit: { context: 262144, output: 16384 }, - }, - "deepseek-r1-distill-qwen-32b": { - id: "deepseek-r1-distill-qwen-32b", - name: "DeepSeek R1 Distill Qwen 32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2025-01-01", - last_updated: "2025-01-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.287, output: 0.861 }, - limit: { context: 32768, output: 16384 }, - }, - "qwen-deep-research": { - id: "qwen-deep-research", - name: "Qwen Deep Research", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-01", - last_updated: "2024-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 7.742, output: 23.367 }, - limit: { context: 1000000, output: 32768 }, - }, - "qwen3-vl-plus": { - id: "qwen3-vl-plus", - name: "Qwen3-VL Plus", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-23", - last_updated: "2025-09-23", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.143353, output: 1.433525, reasoning: 4.300576 }, - limit: { context: 262144, output: 32768 }, - }, - "qwen2-5-math-72b-instruct": { - id: "qwen2-5-math-72b-instruct", - name: "Qwen2.5-Math 72B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.574, output: 1.721 }, - limit: { context: 4096, output: 3072 }, - }, - "qwen-plus": { - id: "qwen-plus", - name: "Qwen Plus", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-01-25", - last_updated: "2025-09-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.115, output: 0.287, reasoning: 1.147 }, - limit: { context: 1000000, output: 32768 }, - }, - "minimax-m2.5": { - id: "minimax-m2.5", - name: "MiniMax-M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 204800, output: 131072 }, - }, - "qwen2-5-32b-instruct": { - id: "qwen2-5-32b-instruct", - name: "Qwen2.5 32B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.287, output: 0.861 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-next-80b-a3b-instruct": { - id: "qwen3-next-80b-a3b-instruct", - name: "Qwen3-Next 80B-A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09", - last_updated: "2025-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.144, output: 0.574 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen3.5-plus": { - id: "qwen3.5-plus", - name: "Qwen3.5 Plus", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2026-02-16", - last_updated: "2026-02-16", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: false, - cost: { input: 0.573, output: 3.44, reasoning: 3.44 }, - limit: { context: 1000000, output: 65536 }, - }, - "qwen3-max": { - id: "qwen3-max", - name: "Qwen3 Max", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-09-23", - last_updated: "2025-09-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.861, output: 3.441 }, - limit: { context: 262144, output: 65536 }, - }, - "qwen3-omni-flash": { - id: "qwen3-omni-flash", - name: "Qwen3-Omni Flash", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image", "audio", "video"], output: ["text", "audio"] }, - open_weights: false, - cost: { input: 0.058, output: 0.23, input_audio: 3.584, output_audio: 7.168 }, - limit: { context: 65536, output: 16384 }, - }, - "qwen-math-turbo": { - id: "qwen-math-turbo", - name: "Qwen Math Turbo", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09-19", - last_updated: "2024-09-19", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.287, output: 0.861 }, - limit: { context: 4096, output: 3072 }, - }, - "qwen-flash": { - id: "qwen-flash", - name: "Qwen Flash", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-07-28", - last_updated: "2025-07-28", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.022, output: 0.216 }, - limit: { context: 1000000, output: 32768 }, - }, - "qwen2-5-72b-instruct": { - id: "qwen2-5-72b-instruct", - name: "Qwen2.5 72B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-09", - last_updated: "2024-09", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.574, output: 1.721 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-omni-flash-realtime": { - id: "qwen3-omni-flash-realtime", - name: "Qwen3-Omni Flash Realtime", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-09-15", - last_updated: "2025-09-15", - modalities: { input: ["text", "image", "audio"], output: ["text", "audio"] }, - open_weights: false, - cost: { input: 0.23, output: 0.918, input_audio: 3.584, output_audio: 7.168 }, - limit: { context: 65536, output: 16384 }, - }, - "qwen-vl-ocr": { - id: "qwen-vl-ocr", - name: "Qwen-VL OCR", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-04", - release_date: "2024-10-28", - last_updated: "2025-04-13", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: false, - cost: { input: 0.717, output: 0.717 }, - limit: { context: 34096, output: 4096 }, - }, - "qwq-plus": { - id: "qwq-plus", - name: "QwQ Plus", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2025-03-05", - last_updated: "2025-03-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.23, output: 0.574 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-vl-235b-a22b": { - id: "qwen3-vl-235b-a22b", - name: "Qwen3-VL 235B-A22B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-04", - last_updated: "2025-04", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.286705, output: 1.14682, reasoning: 2.867051 }, - limit: { context: 131072, output: 32768 }, - }, - "qwen-mt-turbo": { - id: "qwen-mt-turbo", - name: "Qwen-MT Turbo", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - temperature: true, - knowledge: "2024-04", - release_date: "2025-01", - last_updated: "2025-01", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.101, output: 0.28 }, - limit: { context: 16384, output: 8192 }, - }, - "qwen2-5-coder-32b-instruct": { - id: "qwen2-5-coder-32b-instruct", - name: "Qwen2.5-Coder 32B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2024-04", - release_date: "2024-11", - last_updated: "2024-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.287, output: 0.861 }, - limit: { context: 131072, output: 8192 }, - }, - "qwen3-coder-plus": { - id: "qwen3-coder-plus", - name: "Qwen3 Coder Plus", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: "2025-04", - release_date: "2025-07-23", - last_updated: "2025-07-23", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 1, output: 5 }, - limit: { context: 1048576, output: 65536 }, - }, - "kimi/kimi-k2.5": { - id: "kimi/kimi-k2.5", - name: "kimi/kimi-k2.5", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: false, - knowledge: "2025-01", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3, cache_read: 0.1 }, - limit: { context: 262144, output: 262144 }, - }, - "siliconflow/deepseek-r1-0528": { - id: "siliconflow/deepseek-r1-0528", - name: "siliconflow/deepseek-r1-0528", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-05-28", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.5, output: 2.18 }, - limit: { context: 163840, output: 32768 }, - }, - "siliconflow/deepseek-v3-0324": { - id: "siliconflow/deepseek-v3-0324", - name: "siliconflow/deepseek-v3-0324", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2024-12-26", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.25, output: 1 }, - limit: { context: 163840, output: 163840 }, - }, - "siliconflow/deepseek-v3.1-terminus": { - id: "siliconflow/deepseek-v3.1-terminus", - name: "siliconflow/deepseek-v3.1-terminus", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-09-29", - last_updated: "2025-11-25", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 1 }, - limit: { context: 163840, output: 65536 }, - }, - "siliconflow/deepseek-v3.2": { - id: "siliconflow/deepseek-v3.2", - name: "siliconflow/deepseek-v3.2", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-03", - last_updated: "2025-12-03", - modalities: { input: ["text"], output: ["text"] }, - open_weights: false, - cost: { input: 0.27, output: 0.42 }, - limit: { context: 163840, output: 65536 }, - }, - "MiniMax/MiniMax-M2.5": { - id: "MiniMax/MiniMax-M2.5", - name: "MiniMax M2.5", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - release_date: "2026-02-12", - last_updated: "2026-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.301, output: 1.205 }, - limit: { context: 204800, output: 131072 }, - }, - }, - }, - chutes: { - id: "chutes", - env: ["CHUTES_API_KEY"], - npm: "@ai-sdk/openai-compatible", - api: "https://llm.chutes.ai/v1", - name: "Chutes", - doc: "https://llm.chutes.ai/v1/models", - models: { - "zai-org/GLM-4.7-FP8": { - id: "zai-org/GLM-4.7-FP8", - name: "GLM 4.7 FP8", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 202752, output: 65535 }, - }, - "zai-org/GLM-4.5-Air": { - id: "zai-org/GLM-4.5-Air", - name: "GLM 4.5 Air", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.22 }, - limit: { context: 131072, output: 131072 }, - }, - "zai-org/GLM-4.7-Flash": { - id: "zai-org/GLM-4.7-Flash", - name: "GLM 4.7 Flash", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.35 }, - limit: { context: 202752, output: 65535 }, - }, - "zai-org/GLM-4.7-TEE": { - id: "zai-org/GLM-4.7-TEE", - name: "GLM 4.7 TEE", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 1.5 }, - limit: { context: 202752, output: 65535 }, - }, - "zai-org/GLM-4.6-TEE": { - id: "zai-org/GLM-4.6-TEE", - name: "GLM 4.6 TEE", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.35, output: 1.5 }, - limit: { context: 202752, output: 65536 }, - }, - "zai-org/GLM-4.5-FP8": { - id: "zai-org/GLM-4.5-FP8", - name: "GLM 4.5 FP8", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 131072, output: 65536 }, - }, - "zai-org/GLM-5-TEE": { - id: "zai-org/GLM-5-TEE", - name: "GLM 5 TEE", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-14", - last_updated: "2026-02-14", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.75, output: 2.5 }, - limit: { context: 202752, output: 65535 }, - }, - "zai-org/GLM-4.6V": { - id: "zai-org/GLM-4.6V", - name: "GLM 4.6V", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 0.9 }, - limit: { context: 131072, output: 65536 }, - }, - "zai-org/GLM-4.6-FP8": { - id: "zai-org/GLM-4.6-FP8", - name: "GLM 4.6 FP8", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 202752, output: 65535 }, - }, - "zai-org/GLM-4.5-TEE": { - id: "zai-org/GLM-4.5-TEE", - name: "GLM 4.5 TEE", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.35, output: 1.55 }, - limit: { context: 131072, output: 65536 }, - }, - "zai-org/GLM-5-Turbo": { - id: "zai-org/GLM-5-Turbo", - name: "GLM 5 Turbo", - family: "glm", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-03-11", - last_updated: "2026-03-11", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.49, output: 1.96, cache_read: 0.245 }, - limit: { context: 202752, output: 65535 }, - }, - "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16": { - id: "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16", - name: "NVIDIA Nemotron 3 Nano 30B A3B BF16", - family: "nemotron", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.24 }, - limit: { context: 262144, output: 262144 }, - }, - "NousResearch/Hermes-4.3-36B": { - id: "NousResearch/Hermes-4.3-36B", - name: "Hermes 4.3 36B", - family: "nousresearch", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.39 }, - limit: { context: 32768, output: 8192 }, - }, - "NousResearch/DeepHermes-3-Mistral-24B-Preview": { - id: "NousResearch/DeepHermes-3-Mistral-24B-Preview", - name: "DeepHermes 3 Mistral 24B Preview", - family: "nousresearch", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.1 }, - limit: { context: 32768, output: 32768 }, - }, - "NousResearch/Hermes-4-14B": { - id: "NousResearch/Hermes-4-14B", - name: "Hermes 4 14B", - family: "nousresearch", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0.05 }, - limit: { context: 40960, output: 40960 }, - }, - "NousResearch/Hermes-4-405B-FP8-TEE": { - id: "NousResearch/Hermes-4-405B-FP8-TEE", - name: "Hermes 4 405B FP8 TEE", - family: "nousresearch", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 131072, output: 65536 }, - }, - "NousResearch/Hermes-4-70B": { - id: "NousResearch/Hermes-4-70B", - name: "Hermes 4 70B", - family: "nousresearch", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.11, output: 0.38 }, - limit: { context: 131072, output: 131072 }, - }, - "XiaomiMiMo/MiMo-V2-Flash": { - id: "XiaomiMiMo/MiMo-V2-Flash", - name: "MiMo V2 Flash", - family: "mimo", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: false, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.09, output: 0.29 }, - limit: { context: 262144, output: 32000 }, - }, - "MiniMaxAI/MiniMax-M2.5-TEE": { - id: "MiniMaxAI/MiniMax-M2.5-TEE", - name: "MiniMax M2.5 TEE", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-15", - last_updated: "2026-02-15", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 196608, output: 65536 }, - }, - "MiniMaxAI/MiniMax-M2.1-TEE": { - id: "MiniMaxAI/MiniMax-M2.1-TEE", - name: "MiniMax M2.1 TEE", - family: "minimax", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 1.12 }, - limit: { context: 196608, output: 65536 }, - }, - "deepseek-ai/DeepSeek-V3.1-Terminus-TEE": { - id: "deepseek-ai/DeepSeek-V3.1-Terminus-TEE", - name: "DeepSeek V3.1 Terminus TEE", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.23, output: 0.9 }, - limit: { context: 163840, output: 65536 }, - }, - "deepseek-ai/DeepSeek-V3.2-TEE": { - id: "deepseek-ai/DeepSeek-V3.2-TEE", - name: "DeepSeek V3.2 TEE", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 0.38, cache_read: 0.125 }, - limit: { context: 163840, output: 65536 }, - }, - "deepseek-ai/DeepSeek-V3-0324-TEE": { - id: "deepseek-ai/DeepSeek-V3-0324-TEE", - name: "DeepSeek V3 0324 TEE", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.19, output: 0.87, cache_read: 0.095 }, - limit: { context: 163840, output: 65536 }, - }, - "deepseek-ai/DeepSeek-V3.2-Speciale-TEE": { - id: "deepseek-ai/DeepSeek-V3.2-Speciale-TEE", - name: "DeepSeek V3.2 Speciale TEE", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: false, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.27, output: 0.41 }, - limit: { context: 163840, output: 65536 }, - }, - "deepseek-ai/DeepSeek-R1-TEE": { - id: "deepseek-ai/DeepSeek-R1-TEE", - name: "DeepSeek R1 TEE", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: false, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 163840, output: 163840 }, - }, - "deepseek-ai/DeepSeek-V3": { - id: "deepseek-ai/DeepSeek-V3", - name: "DeepSeek V3", - family: "deepseek", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 163840, output: 163840 }, - }, - "deepseek-ai/DeepSeek-R1-Distill-Llama-70B": { - id: "deepseek-ai/DeepSeek-R1-Distill-Llama-70B", - name: "DeepSeek R1 Distill Llama 70B", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.11 }, - limit: { context: 131072, output: 131072 }, - }, - "deepseek-ai/DeepSeek-V3.1-TEE": { - id: "deepseek-ai/DeepSeek-V3.1-TEE", - name: "DeepSeek V3.1 TEE", - family: "deepseek", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.2, output: 0.8 }, - limit: { context: 163840, output: 65536 }, - }, - "deepseek-ai/DeepSeek-R1-0528-TEE": { - id: "deepseek-ai/DeepSeek-R1-0528-TEE", - name: "DeepSeek R1 0528 TEE", - family: "deepseek-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 1.75 }, - limit: { context: 163840, output: 65536 }, - }, - "rednote-hilab/dots.ocr": { - id: "rednote-hilab/dots.ocr", - name: "dots.ocr", - family: "rednote", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0.01, cache_read: 0.005 }, - limit: { context: 131072, output: 131072 }, - }, - "unsloth/Mistral-Nemo-Instruct-2407": { - id: "unsloth/Mistral-Nemo-Instruct-2407", - name: "Mistral Nemo Instruct 2407", - family: "unsloth", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.04, cache_read: 0.01 }, - limit: { context: 131072, output: 131072 }, - }, - "unsloth/Mistral-Small-24B-Instruct-2501": { - id: "unsloth/Mistral-Small-24B-Instruct-2501", - name: "Mistral Small 24B Instruct 2501", - family: "unsloth", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.11 }, - limit: { context: 32768, output: 32768 }, - }, - "unsloth/gemma-3-12b-it": { - id: "unsloth/gemma-3-12b-it", - name: "gemma 3 12b it", - family: "unsloth", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.1 }, - limit: { context: 131072, output: 131072 }, - }, - "unsloth/gemma-3-4b-it": { - id: "unsloth/gemma-3-4b-it", - name: "gemma 3 4b it", - family: "unsloth", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0.03 }, - limit: { context: 96000, output: 96000 }, - }, - "unsloth/gemma-3-27b-it": { - id: "unsloth/gemma-3-27b-it", - name: "gemma 3 27b it", - family: "unsloth", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.15, cache_read: 0.02 }, - limit: { context: 128000, output: 65536 }, - }, - "unsloth/Llama-3.2-1B-Instruct": { - id: "unsloth/Llama-3.2-1B-Instruct", - name: "Llama 3.2 1B Instruct", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0.01, cache_read: 0.005 }, - limit: { context: 32768, output: 8192 }, - }, - "unsloth/Llama-3.2-3B-Instruct": { - id: "unsloth/Llama-3.2-3B-Instruct", - name: "Llama 3.2 3B Instruct", - family: "unsloth", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-02-12", - last_updated: "2025-02-12", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0.01, cache_read: 0.005 }, - limit: { context: 16384, output: 16384 }, - }, - "moonshotai/Kimi-K2-Instruct-0905": { - id: "moonshotai/Kimi-K2-Instruct-0905", - name: "Kimi K2 Instruct 0905", - family: "kimi", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.39, output: 1.9, cache_read: 0.195 }, - limit: { context: 262144, output: 262144 }, - }, - "moonshotai/Kimi-K2.5-TEE": { - id: "moonshotai/Kimi-K2.5-TEE", - name: "Kimi K2.5 TEE", - family: "kimi", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - knowledge: "2024-10", - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text", "image", "video"], output: ["text"] }, - open_weights: true, - cost: { input: 0.6, output: 3 }, - limit: { context: 262144, output: 65535 }, - }, - "moonshotai/Kimi-K2-Thinking-TEE": { - id: "moonshotai/Kimi-K2-Thinking-TEE", - name: "Kimi K2 Thinking TEE", - family: "kimi-thinking", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.4, output: 1.75 }, - limit: { context: 262144, output: 65535 }, - }, - "Qwen/Qwen3-30B-A3B": { - id: "Qwen/Qwen3-30B-A3B", - name: "Qwen3 30B A3B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.22 }, - limit: { context: 40960, output: 40960 }, - }, - "Qwen/Qwen3-30B-A3B-Instruct-2507": { - id: "Qwen/Qwen3-30B-A3B-Instruct-2507", - name: "Qwen3 30B A3B Instruct 2507", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.08, output: 0.33 }, - limit: { context: 262144, output: 262144 }, - }, - "Qwen/Qwen3-VL-235B-A22B-Instruct": { - id: "Qwen/Qwen3-VL-235B-A22B-Instruct", - name: "Qwen3 VL 235B A22B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 262144, output: 262144 }, - }, - "Qwen/Qwen3.5-397B-A17B-TEE": { - id: "Qwen/Qwen3.5-397B-A17B-TEE", - name: "Qwen3.5 397B A17B TEE", - family: "qwen", - attachment: true, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2026-02-18", - last_updated: "2026-02-18", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.15 }, - limit: { context: 262144, output: 65536 }, - }, - "Qwen/Qwen3-32B": { - id: "Qwen/Qwen3-32B", - name: "Qwen3 32B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.08, output: 0.24, cache_read: 0.04 }, - limit: { context: 40960, output: 40960 }, - }, - "Qwen/Qwen3-Next-80B-A3B-Instruct": { - id: "Qwen/Qwen3-Next-80B-A3B-Instruct", - name: "Qwen3 Next 80B A3B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.8 }, - limit: { context: 262144, output: 262144 }, - }, - "Qwen/Qwen3-235B-A22B-Thinking-2507": { - id: "Qwen/Qwen3-235B-A22B-Thinking-2507", - name: "Qwen3 235B A22B Thinking 2507", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.11, output: 0.6 }, - limit: { context: 262144, output: 262144 }, - }, - "Qwen/Qwen3-Coder-Next": { - id: "Qwen/Qwen3-Coder-Next", - name: "Qwen3 Coder Next", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-02-05", - last_updated: "2026-02-05", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.07, output: 0.3 }, - limit: { context: 262144, output: 65536 }, - }, - "Qwen/Qwen2.5-Coder-32B-Instruct": { - id: "Qwen/Qwen2.5-Coder-32B-Instruct", - name: "Qwen2.5 Coder 32B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.11 }, - limit: { context: 32768, output: 32768 }, - }, - "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8-TEE": { - id: "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8-TEE", - name: "Qwen3 Coder 480B A35B Instruct FP8 TEE", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.22, output: 0.95, cache_read: 0.11 }, - limit: { context: 262144, output: 262144 }, - }, - "Qwen/Qwen2.5-72B-Instruct": { - id: "Qwen/Qwen2.5-72B-Instruct", - name: "Qwen2.5 72B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.13, output: 0.52 }, - limit: { context: 32768, output: 32768 }, - }, - "Qwen/Qwen3-235B-A22B-Instruct-2507-TEE": { - id: "Qwen/Qwen3-235B-A22B-Instruct-2507-TEE", - name: "Qwen3 235B A22B Instruct 2507 TEE", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.08, output: 0.55, cache_read: 0.04 }, - limit: { context: 262144, output: 65536 }, - }, - "Qwen/Qwen3-235B-A22B": { - id: "Qwen/Qwen3-235B-A22B", - name: "Qwen3 235B A22B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 40960, output: 40960 }, - }, - "Qwen/Qwen2.5-VL-72B-Instruct-TEE": { - id: "Qwen/Qwen2.5-VL-72B-Instruct-TEE", - name: "Qwen2.5 VL 72B Instruct TEE", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.15, output: 0.6 }, - limit: { context: 32768, output: 32768 }, - }, - "Qwen/Qwen3Guard-Gen-0.6B": { - id: "Qwen/Qwen3Guard-Gen-0.6B", - name: "Qwen3Guard Gen 0.6B", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.01, output: 0.01, cache_read: 0.005 }, - limit: { context: 32768, output: 8192 }, - }, - "Qwen/Qwen3-14B": { - id: "Qwen/Qwen3-14B", - name: "Qwen3 14B", - family: "qwen", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.22 }, - limit: { context: 40960, output: 40960 }, - }, - "Qwen/Qwen2.5-VL-32B-Instruct": { - id: "Qwen/Qwen2.5-VL-32B-Instruct", - name: "Qwen2.5 VL 32B Instruct", - family: "qwen", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.22 }, - limit: { context: 16384, output: 16384 }, - }, - "tngtech/DeepSeek-R1T-Chimera": { - id: "tngtech/DeepSeek-R1T-Chimera", - name: "DeepSeek R1T Chimera", - family: "tngtech", - attachment: false, - reasoning: true, - tool_call: false, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2 }, - limit: { context: 163840, output: 163840 }, - }, - "tngtech/DeepSeek-TNG-R1T2-Chimera": { - id: "tngtech/DeepSeek-TNG-R1T2-Chimera", - name: "DeepSeek TNG R1T2 Chimera", - family: "tngtech", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 0.85 }, - limit: { context: 163840, output: 163840 }, - }, - "tngtech/TNG-R1T-Chimera-Turbo": { - id: "tngtech/TNG-R1T-Chimera-Turbo", - name: "TNG R1T Chimera Turbo", - attachment: false, - reasoning: true, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01-27", - last_updated: "2026-01-27", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.22, output: 0.6 }, - limit: { context: 163840, output: 65536 }, - }, - "tngtech/TNG-R1T-Chimera-TEE": { - id: "tngtech/TNG-R1T-Chimera-TEE", - name: "TNG R1T Chimera TEE", - family: "tngtech", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.25, output: 0.85 }, - limit: { context: 163840, output: 65536 }, - }, - "mistralai/Devstral-2-123B-Instruct-2512-TEE": { - id: "mistralai/Devstral-2-123B-Instruct-2512-TEE", - name: "Devstral 2 123B Instruct 2512 TEE", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2026-01-10", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.05, output: 0.22 }, - limit: { context: 262144, output: 65536 }, - }, - "openai/gpt-oss-120b-TEE": { - id: "openai/gpt-oss-120b-TEE", - name: "gpt oss 120b TEE", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.04, output: 0.18 }, - limit: { context: 131072, output: 65536 }, - }, - "openai/gpt-oss-20b": { - id: "openai/gpt-oss-20b", - name: "gpt oss 20b", - family: "gpt-oss", - attachment: false, - reasoning: true, - tool_call: true, - interleaved: { field: "reasoning_content" }, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.02, output: 0.1 }, - limit: { context: 131072, output: 131072 }, - }, - "chutesai/Mistral-Small-3.2-24B-Instruct-2506": { - id: "chutesai/Mistral-Small-3.2-24B-Instruct-2506", - name: "Mistral Small 3.2 24B Instruct 2506", - family: "chutesai", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.06, output: 0.18 }, - limit: { context: 131072, output: 131072 }, - }, - "chutesai/Mistral-Small-3.1-24B-Instruct-2503": { - id: "chutesai/Mistral-Small-3.1-24B-Instruct-2503", - name: "Mistral Small 3.1 24B Instruct 2503", - family: "chutesai", - attachment: false, - reasoning: false, - tool_call: true, - structured_output: true, - temperature: true, - release_date: "2025-12-29", - last_updated: "2026-01-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.03, output: 0.11, cache_read: 0.015 }, - limit: { context: 131072, output: 131072 }, - }, - "miromind-ai/MiroThinker-v1.5-235B": { - id: "miromind-ai/MiroThinker-v1.5-235B", - name: "MiroThinker V1.5 235B", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: false, - temperature: true, - release_date: "2026-01-10", - last_updated: "2026-01-10", - modalities: { input: ["text"], output: ["text"] }, - open_weights: true, - cost: { input: 0.3, output: 1.2, cache_read: 0.15 }, - limit: { context: 262144, output: 8192 }, - }, - "OpenGVLab/InternVL3-78B-TEE": { - id: "OpenGVLab/InternVL3-78B-TEE", - name: "InternVL3 78B TEE", - family: "opengvlab", - attachment: false, - reasoning: false, - tool_call: false, - structured_output: true, - temperature: true, - release_date: "2025-01-06", - last_updated: "2026-01-10", - modalities: { input: ["text", "image"], output: ["text"] }, - open_weights: true, - cost: { input: 0.1, output: 0.39 }, - limit: { context: 32768, output: 32768 }, - }, - }, - }, -} as const From 11edb6e87d48ff92e4dabcaf23a7be2032958538 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 4 Apr 2026 00:54:03 +0200 Subject: [PATCH 070/616] notes on v2 (#20941) --- packages/opencode/specs/v2.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 packages/opencode/specs/v2.md diff --git a/packages/opencode/specs/v2.md b/packages/opencode/specs/v2.md new file mode 100644 index 000000000000..55d0f026751c --- /dev/null +++ b/packages/opencode/specs/v2.md @@ -0,0 +1,13 @@ +# 2.0 + +What we would change if we could + +## Keybindings vs. Keymappings +Make it `keymappings`, closer to neovim. Can be layered like `abc`. Commands don't define their binding, but have an id that a key can be mapped to like + +```ts +{ key: "ctrl+w", cmd: string | function, description } +``` + +_Why_ +Currently its keybindings that have an `id` like `message_redo` and then a command can use that or define it's own binding. While some keybindings are just used with `.match` in arbitrary key handlers and there is no info what the key is used for, except the binding id maybe. It also is unknown in which context/scope what binding is active, so a plugin like `which-key` is nearly impossible to get right. From 2f96188c3289bb863cb3390a7e624edd2fce0a96 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Fri, 3 Apr 2026 22:55:05 +0000 Subject: [PATCH 071/616] chore: generate --- packages/opencode/specs/v2.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/opencode/specs/v2.md b/packages/opencode/specs/v2.md index 55d0f026751c..66b4d2dea477 100644 --- a/packages/opencode/specs/v2.md +++ b/packages/opencode/specs/v2.md @@ -3,7 +3,8 @@ What we would change if we could ## Keybindings vs. Keymappings -Make it `keymappings`, closer to neovim. Can be layered like `abc`. Commands don't define their binding, but have an id that a key can be mapped to like + +Make it `keymappings`, closer to neovim. Can be layered like `abc`. Commands don't define their binding, but have an id that a key can be mapped to like ```ts { key: "ctrl+w", cmd: string | function, description } From 1d55759d9f735cfae5990c3271c111a9991e6d32 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 3 Apr 2026 20:24:24 -0400 Subject: [PATCH 072/616] refactor(provider): stop custom loaders using facades (#20776) Co-authored-by: luanweslley77 <213105503+luanweslley77@users.noreply.github.com> --- packages/opencode/src/provider/provider.ts | 1177 +++++++++-------- .../opencode/test/provider/provider.test.ts | 210 +++ 2 files changed, 802 insertions(+), 585 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index afd69f9e7072..924d13312a68 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -152,7 +152,7 @@ export namespace Provider { type CustomModelLoader = (sdk: any, modelID: string, options?: Record) => Promise type CustomVarsLoader = (options: Record) => Record type CustomDiscoverModels = () => Promise> - type CustomLoader = (provider: Info) => Promise<{ + type CustomLoader = (provider: Info) => Effect.Effect<{ autoload: boolean getModel?: CustomModelLoader vars?: CustomVarsLoader @@ -160,632 +160,629 @@ export namespace Provider { discoverModels?: CustomDiscoverModels }> + type CustomDep = { + auth: (id: string) => Effect.Effect + config: () => Effect.Effect + } + function useLanguageModel(sdk: any) { return sdk.responses === undefined && sdk.chat === undefined } - const CUSTOM_LOADERS: Record = { - async anthropic() { - return { - autoload: false, - options: { - headers: { - "anthropic-beta": "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14", + function custom(dep: CustomDep): Record { + return { + anthropic: () => + Effect.succeed({ + autoload: false, + options: { + headers: { + "anthropic-beta": "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14", + }, }, - }, - } - }, - async opencode(input) { - const hasKey = await (async () => { + }), + opencode: Effect.fnUntraced(function* (input: Info) { const env = Env.all() - if (input.env.some((item) => env[item])) return true - if (await Auth.get(input.id)) return true - const config = await Config.get() - if (config.provider?.["opencode"]?.options?.apiKey) return true - return false - })() - - if (!hasKey) { - for (const [key, value] of Object.entries(input.models)) { - if (value.cost.input === 0) continue - delete input.models[key] + const hasKey = iife(() => { + if (input.env.some((item) => env[item])) return true + return false + }) + const ok = + hasKey || + Boolean(yield* dep.auth(input.id)) || + Boolean((yield* dep.config()).provider?.["opencode"]?.options?.apiKey) + + if (!ok) { + for (const [key, value] of Object.entries(input.models)) { + if (value.cost.input === 0) continue + delete input.models[key] + } } - } - return { - autoload: Object.keys(input.models).length > 0, - options: hasKey ? {} : { apiKey: "public" }, - } - }, - openai: async () => { - return { - autoload: false, - async getModel(sdk: any, modelID: string, _options?: Record) { - return sdk.responses(modelID) - }, - options: {}, - } - }, - xai: async () => { - return { - autoload: false, - async getModel(sdk: any, modelID: string, _options?: Record) { - return sdk.responses(modelID) - }, - options: {}, - } - }, - "github-copilot": async () => { - return { - autoload: false, - async getModel(sdk: any, modelID: string, _options?: Record) { - if (useLanguageModel(sdk)) return sdk.languageModel(modelID) - return shouldUseCopilotResponsesApi(modelID) ? sdk.responses(modelID) : sdk.chat(modelID) - }, - options: {}, - } - }, - azure: async (provider) => { - const resource = iife(() => { - const name = provider.options?.resourceName - if (typeof name === "string" && name.trim() !== "") return name - return Env.get("AZURE_RESOURCE_NAME") - }) - - return { - autoload: false, - async getModel(sdk: any, modelID: string, options?: Record) { - if (useLanguageModel(sdk)) return sdk.languageModel(modelID) - if (options?.["useCompletionUrls"]) { - return sdk.chat(modelID) - } else { + return { + autoload: Object.keys(input.models).length > 0, + options: ok ? {} : { apiKey: "public" }, + } + }), + openai: () => + Effect.succeed({ + autoload: false, + async getModel(sdk: any, modelID: string, _options?: Record) { return sdk.responses(modelID) - } - }, - options: {}, - vars(_options) { - return { - ...(resource && { AZURE_RESOURCE_NAME: resource }), - } - }, - } - }, - "azure-cognitive-services": async () => { - const resourceName = Env.get("AZURE_COGNITIVE_SERVICES_RESOURCE_NAME") - return { - autoload: false, - async getModel(sdk: any, modelID: string, options?: Record) { - if (useLanguageModel(sdk)) return sdk.languageModel(modelID) - if (options?.["useCompletionUrls"]) { - return sdk.chat(modelID) - } else { + }, + options: {}, + }), + xai: () => + Effect.succeed({ + autoload: false, + async getModel(sdk: any, modelID: string, _options?: Record) { return sdk.responses(modelID) - } - }, - options: { - baseURL: resourceName ? `https://${resourceName}.cognitiveservices.azure.com/openai` : undefined, - }, - } - }, - "amazon-bedrock": async () => { - const config = await Config.get() - const providerConfig = config.provider?.["amazon-bedrock"] - - const auth = await Auth.get("amazon-bedrock") - - // Region precedence: 1) config file, 2) env var, 3) default - const configRegion = providerConfig?.options?.region - const envRegion = Env.get("AWS_REGION") - const defaultRegion = configRegion ?? envRegion ?? "us-east-1" - - // Profile: config file takes precedence over env var - const configProfile = providerConfig?.options?.profile - const envProfile = Env.get("AWS_PROFILE") - const profile = configProfile ?? envProfile - - const awsAccessKeyId = Env.get("AWS_ACCESS_KEY_ID") - - // TODO: Using process.env directly because Env.set only updates a process.env shallow copy, - // until the scope of the Env API is clarified (test only or runtime?) - const awsBearerToken = iife(() => { - const envToken = process.env.AWS_BEARER_TOKEN_BEDROCK - if (envToken) return envToken - if (auth?.type === "api") { - process.env.AWS_BEARER_TOKEN_BEDROCK = auth.key - return auth.key - } - return undefined - }) + }, + options: {}, + }), + "github-copilot": () => + Effect.succeed({ + autoload: false, + async getModel(sdk: any, modelID: string, _options?: Record) { + if (useLanguageModel(sdk)) return sdk.languageModel(modelID) + return shouldUseCopilotResponsesApi(modelID) ? sdk.responses(modelID) : sdk.chat(modelID) + }, + options: {}, + }), + azure: (provider) => { + const resource = iife(() => { + const name = provider.options?.resourceName + if (typeof name === "string" && name.trim() !== "") return name + return Env.get("AZURE_RESOURCE_NAME") + }) - const awsWebIdentityTokenFile = Env.get("AWS_WEB_IDENTITY_TOKEN_FILE") + return Effect.succeed({ + autoload: false, + async getModel(sdk: any, modelID: string, options?: Record) { + if (useLanguageModel(sdk)) return sdk.languageModel(modelID) + if (options?.["useCompletionUrls"]) { + return sdk.chat(modelID) + } else { + return sdk.responses(modelID) + } + }, + options: {}, + vars(_options) { + return { + ...(resource && { AZURE_RESOURCE_NAME: resource }), + } + }, + }) + }, + "azure-cognitive-services": () => { + const resourceName = Env.get("AZURE_COGNITIVE_SERVICES_RESOURCE_NAME") + return Effect.succeed({ + autoload: false, + async getModel(sdk: any, modelID: string, options?: Record) { + if (useLanguageModel(sdk)) return sdk.languageModel(modelID) + if (options?.["useCompletionUrls"]) { + return sdk.chat(modelID) + } else { + return sdk.responses(modelID) + } + }, + options: { + baseURL: resourceName ? `https://${resourceName}.cognitiveservices.azure.com/openai` : undefined, + }, + }) + }, + "amazon-bedrock": Effect.fnUntraced(function* () { + const providerConfig = (yield* dep.config()).provider?.["amazon-bedrock"] + const auth = yield* dep.auth("amazon-bedrock") + + // Region precedence: 1) config file, 2) env var, 3) default + const configRegion = providerConfig?.options?.region + const envRegion = Env.get("AWS_REGION") + const defaultRegion = configRegion ?? envRegion ?? "us-east-1" + + // Profile: config file takes precedence over env var + const configProfile = providerConfig?.options?.profile + const envProfile = Env.get("AWS_PROFILE") + const profile = configProfile ?? envProfile + + const awsAccessKeyId = Env.get("AWS_ACCESS_KEY_ID") + + // TODO: Using process.env directly because Env.set only updates a process.env shallow copy, + // until the scope of the Env API is clarified (test only or runtime?) + const awsBearerToken = iife(() => { + const envToken = process.env.AWS_BEARER_TOKEN_BEDROCK + if (envToken) return envToken + if (auth?.type === "api") { + process.env.AWS_BEARER_TOKEN_BEDROCK = auth.key + return auth.key + } + return undefined + }) - const containerCreds = Boolean( - process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI, - ) + const awsWebIdentityTokenFile = Env.get("AWS_WEB_IDENTITY_TOKEN_FILE") - if (!profile && !awsAccessKeyId && !awsBearerToken && !awsWebIdentityTokenFile && !containerCreds) - return { autoload: false } + const containerCreds = Boolean( + process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI, + ) - const providerOptions: AmazonBedrockProviderSettings = { - region: defaultRegion, - } + if (!profile && !awsAccessKeyId && !awsBearerToken && !awsWebIdentityTokenFile && !containerCreds) + return { autoload: false } - // Only use credential chain if no bearer token exists - // Bearer token takes precedence over credential chain (profiles, access keys, IAM roles, web identity tokens) - if (!awsBearerToken) { - // Build credential provider options (only pass profile if specified) - const credentialProviderOptions = profile ? { profile } : {} + const providerOptions: AmazonBedrockProviderSettings = { + region: defaultRegion, + } - providerOptions.credentialProvider = fromNodeProviderChain(credentialProviderOptions) - } + // Only use credential chain if no bearer token exists + // Bearer token takes precedence over credential chain (profiles, access keys, IAM roles, web identity tokens) + if (!awsBearerToken) { + // Build credential provider options (only pass profile if specified) + const credentialProviderOptions = profile ? { profile } : {} - // Add custom endpoint if specified (endpoint takes precedence over baseURL) - const endpoint = providerConfig?.options?.endpoint ?? providerConfig?.options?.baseURL - if (endpoint) { - providerOptions.baseURL = endpoint - } + providerOptions.credentialProvider = fromNodeProviderChain(credentialProviderOptions) + } - return { - autoload: true, - options: providerOptions, - async getModel(sdk: any, modelID: string, options?: Record) { - // Skip region prefixing if model already has a cross-region inference profile prefix - // Models from models.dev may already include prefixes like us., eu., global., etc. - const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."] - if (crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))) { - return sdk.languageModel(modelID) - } + // Add custom endpoint if specified (endpoint takes precedence over baseURL) + const endpoint = providerConfig?.options?.endpoint ?? providerConfig?.options?.baseURL + if (endpoint) { + providerOptions.baseURL = endpoint + } - // Region resolution precedence (highest to lowest): - // 1. options.region from opencode.json provider config - // 2. defaultRegion from AWS_REGION environment variable - // 3. Default "us-east-1" (baked into defaultRegion) - const region = options?.region ?? defaultRegion - - let regionPrefix = region.split("-")[0] - - switch (regionPrefix) { - case "us": { - const modelRequiresPrefix = [ - "nova-micro", - "nova-lite", - "nova-pro", - "nova-premier", - "nova-2", - "claude", - "deepseek", - ].some((m) => modelID.includes(m)) - const isGovCloud = region.startsWith("us-gov") - if (modelRequiresPrefix && !isGovCloud) { - modelID = `${regionPrefix}.${modelID}` - } - break + return { + autoload: true, + options: providerOptions, + async getModel(sdk: any, modelID: string, options?: Record) { + // Skip region prefixing if model already has a cross-region inference profile prefix + // Models from models.dev may already include prefixes like us., eu., global., etc. + const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."] + if (crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))) { + return sdk.languageModel(modelID) } - case "eu": { - const regionRequiresPrefix = [ - "eu-west-1", - "eu-west-2", - "eu-west-3", - "eu-north-1", - "eu-central-1", - "eu-south-1", - "eu-south-2", - ].some((r) => region.includes(r)) - const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "llama3", "pixtral"].some((m) => - modelID.includes(m), - ) - if (regionRequiresPrefix && modelRequiresPrefix) { - modelID = `${regionPrefix}.${modelID}` + + // Region resolution precedence (highest to lowest): + // 1. options.region from opencode.json provider config + // 2. defaultRegion from AWS_REGION environment variable + // 3. Default "us-east-1" (baked into defaultRegion) + const region = options?.region ?? defaultRegion + + let regionPrefix = region.split("-")[0] + + switch (regionPrefix) { + case "us": { + const modelRequiresPrefix = [ + "nova-micro", + "nova-lite", + "nova-pro", + "nova-premier", + "nova-2", + "claude", + "deepseek", + ].some((m) => modelID.includes(m)) + const isGovCloud = region.startsWith("us-gov") + if (modelRequiresPrefix && !isGovCloud) { + modelID = `${regionPrefix}.${modelID}` + } + break } - break - } - case "ap": { - const isAustraliaRegion = ["ap-southeast-2", "ap-southeast-4"].includes(region) - const isTokyoRegion = region === "ap-northeast-1" - if ( - isAustraliaRegion && - ["anthropic.claude-sonnet-4-5", "anthropic.claude-haiku"].some((m) => modelID.includes(m)) - ) { - regionPrefix = "au" - modelID = `${regionPrefix}.${modelID}` - } else if (isTokyoRegion) { - // Tokyo region uses jp. prefix for cross-region inference - const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "nova-pro"].some((m) => + case "eu": { + const regionRequiresPrefix = [ + "eu-west-1", + "eu-west-2", + "eu-west-3", + "eu-north-1", + "eu-central-1", + "eu-south-1", + "eu-south-2", + ].some((r) => region.includes(r)) + const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "llama3", "pixtral"].some((m) => modelID.includes(m), ) - if (modelRequiresPrefix) { - regionPrefix = "jp" + if (regionRequiresPrefix && modelRequiresPrefix) { modelID = `${regionPrefix}.${modelID}` } - } else { - // Other APAC regions use apac. prefix - const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "nova-pro"].some((m) => - modelID.includes(m), - ) - if (modelRequiresPrefix) { - regionPrefix = "apac" + break + } + case "ap": { + const isAustraliaRegion = ["ap-southeast-2", "ap-southeast-4"].includes(region) + const isTokyoRegion = region === "ap-northeast-1" + if ( + isAustraliaRegion && + ["anthropic.claude-sonnet-4-5", "anthropic.claude-haiku"].some((m) => modelID.includes(m)) + ) { + regionPrefix = "au" modelID = `${regionPrefix}.${modelID}` + } else if (isTokyoRegion) { + // Tokyo region uses jp. prefix for cross-region inference + const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "nova-pro"].some((m) => + modelID.includes(m), + ) + if (modelRequiresPrefix) { + regionPrefix = "jp" + modelID = `${regionPrefix}.${modelID}` + } + } else { + // Other APAC regions use apac. prefix + const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "nova-pro"].some((m) => + modelID.includes(m), + ) + if (modelRequiresPrefix) { + regionPrefix = "apac" + modelID = `${regionPrefix}.${modelID}` + } } + break } - break } - } - return sdk.languageModel(modelID) - }, - } - }, - openrouter: async () => { - return { - autoload: false, - options: { - headers: { - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", + return sdk.languageModel(modelID) }, - }, - } - }, - vercel: async () => { - return { - autoload: false, - options: { - headers: { - "http-referer": "https://opencode.ai/", - "x-title": "opencode", + } + }), + openrouter: () => + Effect.succeed({ + autoload: false, + options: { + headers: { + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "opencode", + }, }, - }, - } - }, - "google-vertex": async (provider) => { - const project = - provider.options?.project ?? - Env.get("GOOGLE_CLOUD_PROJECT") ?? - Env.get("GCP_PROJECT") ?? - Env.get("GCLOUD_PROJECT") - - const location = String( - provider.options?.location ?? - Env.get("GOOGLE_VERTEX_LOCATION") ?? - Env.get("GOOGLE_CLOUD_LOCATION") ?? - Env.get("VERTEX_LOCATION") ?? - "us-central1", - ) + }), + vercel: () => + Effect.succeed({ + autoload: false, + options: { + headers: { + "http-referer": "https://opencode.ai/", + "x-title": "opencode", + }, + }, + }), + "google-vertex": (provider) => { + const project = + provider.options?.project ?? + Env.get("GOOGLE_CLOUD_PROJECT") ?? + Env.get("GCP_PROJECT") ?? + Env.get("GCLOUD_PROJECT") + + const location = String( + provider.options?.location ?? + Env.get("GOOGLE_VERTEX_LOCATION") ?? + Env.get("GOOGLE_CLOUD_LOCATION") ?? + Env.get("VERTEX_LOCATION") ?? + "us-central1", + ) - const autoload = Boolean(project) - if (!autoload) return { autoload: false } - return { - autoload: true, - vars(_options: Record) { - const endpoint = location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com` - return { - ...(project && { GOOGLE_VERTEX_PROJECT: project }), - GOOGLE_VERTEX_LOCATION: location, - GOOGLE_VERTEX_ENDPOINT: endpoint, + const autoload = Boolean(project) + if (!autoload) return Effect.succeed({ autoload: false }) + return Effect.succeed({ + autoload: true, + vars(_options: Record) { + const endpoint = + location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com` + return { + ...(project && { GOOGLE_VERTEX_PROJECT: project }), + GOOGLE_VERTEX_LOCATION: location, + GOOGLE_VERTEX_ENDPOINT: endpoint, + } + }, + options: { + project, + location, + fetch: async (input: RequestInfo | URL, init?: RequestInit) => { + const auth = new GoogleAuth() + const client = await auth.getApplicationDefault() + const token = await client.credential.getAccessToken() + + const headers = new Headers(init?.headers) + headers.set("Authorization", `Bearer ${token.token}`) + + return fetch(input, { ...init, headers }) + }, + }, + async getModel(sdk: any, modelID: string) { + const id = String(modelID).trim() + return sdk.languageModel(id) + }, + }) + }, + "google-vertex-anthropic": () => { + const project = Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT") + const location = Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "global" + const autoload = Boolean(project) + if (!autoload) return Effect.succeed({ autoload: false }) + return Effect.succeed({ + autoload: true, + options: { + project, + location, + }, + async getModel(sdk: any, modelID) { + const id = String(modelID).trim() + return sdk.languageModel(id) + }, + }) + }, + "sap-ai-core": Effect.fnUntraced(function* () { + const auth = yield* dep.auth("sap-ai-core") + // TODO: Using process.env directly because Env.set only updates a shallow copy (not process.env), + // until the scope of the Env API is clarified (test only or runtime?) + const envServiceKey = iife(() => { + const envAICoreServiceKey = process.env.AICORE_SERVICE_KEY + if (envAICoreServiceKey) return envAICoreServiceKey + if (auth?.type === "api") { + process.env.AICORE_SERVICE_KEY = auth.key + return auth.key } - }, - options: { - project, - location, - fetch: async (input: RequestInfo | URL, init?: RequestInit) => { - const auth = new GoogleAuth() - const client = await auth.getApplicationDefault() - const token = await client.credential.getAccessToken() - - const headers = new Headers(init?.headers) - headers.set("Authorization", `Bearer ${token.token}`) - - return fetch(input, { ...init, headers }) + return undefined + }) + const deploymentId = process.env.AICORE_DEPLOYMENT_ID + const resourceGroup = process.env.AICORE_RESOURCE_GROUP + + return { + autoload: !!envServiceKey, + options: envServiceKey ? { deploymentId, resourceGroup } : {}, + async getModel(sdk: any, modelID: string) { + return sdk(modelID) }, - }, - async getModel(sdk: any, modelID: string) { - const id = String(modelID).trim() - return sdk.languageModel(id) - }, - } - }, - "google-vertex-anthropic": async () => { - const project = Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT") - const location = Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "global" - const autoload = Boolean(project) - if (!autoload) return { autoload: false } - return { - autoload: true, - options: { - project, - location, - }, - async getModel(sdk: any, modelID) { - const id = String(modelID).trim() - return sdk.languageModel(id) - }, - } - }, - "sap-ai-core": async () => { - const auth = await Auth.get("sap-ai-core") - // TODO: Using process.env directly because Env.set only updates a shallow copy (not process.env), - // until the scope of the Env API is clarified (test only or runtime?) - const envServiceKey = iife(() => { - const envAICoreServiceKey = process.env.AICORE_SERVICE_KEY - if (envAICoreServiceKey) return envAICoreServiceKey - if (auth?.type === "api") { - process.env.AICORE_SERVICE_KEY = auth.key - return auth.key } - return undefined - }) - const deploymentId = process.env.AICORE_DEPLOYMENT_ID - const resourceGroup = process.env.AICORE_RESOURCE_GROUP - - return { - autoload: !!envServiceKey, - options: envServiceKey ? { deploymentId, resourceGroup } : {}, - async getModel(sdk: any, modelID: string) { - return sdk(modelID) - }, - } - }, - zenmux: async () => { - return { - autoload: false, - options: { - headers: { - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", + }), + zenmux: () => + Effect.succeed({ + autoload: false, + options: { + headers: { + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "opencode", + }, }, - }, - } - }, - gitlab: async (input) => { - const instanceUrl = Env.get("GITLAB_INSTANCE_URL") || "https://gitlab.com" - - const auth = await Auth.get(input.id) - const apiKey = await (async () => { - if (auth?.type === "oauth") return auth.access - if (auth?.type === "api") return auth.key - return Env.get("GITLAB_TOKEN") - })() - - const config = await Config.get() - const providerConfig = config.provider?.["gitlab"] - - const aiGatewayHeaders = { - "User-Agent": `opencode/${Installation.VERSION} gitlab-ai-provider/${GITLAB_PROVIDER_VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`, - "anthropic-beta": "context-1m-2025-08-07", - ...(providerConfig?.options?.aiGatewayHeaders || {}), - } + }), + gitlab: Effect.fnUntraced(function* (input: Info) { + const instanceUrl = Env.get("GITLAB_INSTANCE_URL") || "https://gitlab.com" + + const auth = yield* dep.auth(input.id) + const apiKey = yield* Effect.sync(() => { + if (auth?.type === "oauth") return auth.access + if (auth?.type === "api") return auth.key + return Env.get("GITLAB_TOKEN") + }) - const featureFlags = { - duo_agent_platform_agentic_chat: true, - duo_agent_platform: true, - ...(providerConfig?.options?.featureFlags || {}), - } + const providerConfig = (yield* dep.config()).provider?.["gitlab"] - return { - autoload: !!apiKey, - options: { - instanceUrl, - apiKey, - aiGatewayHeaders, - featureFlags, - }, - async getModel(sdk: ReturnType, modelID: string, options?: Record) { - if (modelID.startsWith("duo-workflow-")) { - const workflowRef = options?.workflowRef as string | undefined - // Use the static mapping if it exists, otherwise use duo-workflow with selectedModelRef - const sdkModelID = isWorkflowModel(modelID) ? modelID : "duo-workflow" - const model = sdk.workflowChat(sdkModelID, { - featureFlags, - }) - if (workflowRef) { - model.selectedModelRef = workflowRef - } - return model - } - return sdk.agenticChat(modelID, { - aiGatewayHeaders, - featureFlags, - }) - }, - async discoverModels(): Promise> { - if (!apiKey) { - log.info("gitlab model discovery skipped: no apiKey") - return {} - } + const aiGatewayHeaders = { + "User-Agent": `opencode/${Installation.VERSION} gitlab-ai-provider/${GITLAB_PROVIDER_VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`, + "anthropic-beta": "context-1m-2025-08-07", + ...(providerConfig?.options?.aiGatewayHeaders || {}), + } - try { - const token = apiKey - const getHeaders = (): Record => - auth?.type === "api" ? { "PRIVATE-TOKEN": token } : { Authorization: `Bearer ${token}` } - - log.info("gitlab model discovery starting", { instanceUrl }) - const result = await discoverWorkflowModels( - { instanceUrl, getHeaders }, - { workingDirectory: Instance.directory }, - ) + const featureFlags = { + duo_agent_platform_agentic_chat: true, + duo_agent_platform: true, + ...(providerConfig?.options?.featureFlags || {}), + } - if (!result.models.length) { - log.info("gitlab model discovery skipped: no models found", { - project: result.project - ? { - id: result.project.id, - path: result.project.pathWithNamespace, - } - : null, + return { + autoload: !!apiKey, + options: { + instanceUrl, + apiKey, + aiGatewayHeaders, + featureFlags, + }, + async getModel(sdk: ReturnType, modelID: string, options?: Record) { + if (modelID.startsWith("duo-workflow-")) { + const workflowRef = options?.workflowRef as string | undefined + // Use the static mapping if it exists, otherwise use duo-workflow with selectedModelRef + const sdkModelID = isWorkflowModel(modelID) ? modelID : "duo-workflow" + const model = sdk.workflowChat(sdkModelID, { + featureFlags, }) + if (workflowRef) { + model.selectedModelRef = workflowRef + } + return model + } + return sdk.agenticChat(modelID, { + aiGatewayHeaders, + featureFlags, + }) + }, + async discoverModels(): Promise> { + if (!apiKey) { + log.info("gitlab model discovery skipped: no apiKey") return {} } - const models: Record = {} - for (const m of result.models) { - if (!input.models[m.id]) { - models[m.id] = { - id: ModelID.make(m.id), - providerID: ProviderID.make("gitlab"), - name: `Agent Platform (${m.name})`, - family: "", - api: { - id: m.id, - url: instanceUrl, - npm: "gitlab-ai-provider", - }, - status: "active", - headers: {}, - options: { workflowRef: m.ref }, - cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, - limit: { context: m.context, output: m.output }, - capabilities: { - temperature: false, - reasoning: true, - attachment: true, - toolcall: true, - input: { - text: true, - audio: false, - image: true, - video: false, - pdf: true, + try { + const token = apiKey + const getHeaders = (): Record => + auth?.type === "api" ? { "PRIVATE-TOKEN": token } : { Authorization: `Bearer ${token}` } + + log.info("gitlab model discovery starting", { instanceUrl }) + const result = await discoverWorkflowModels( + { instanceUrl, getHeaders }, + { workingDirectory: Instance.directory }, + ) + + if (!result.models.length) { + log.info("gitlab model discovery skipped: no models found", { + project: result.project + ? { + id: result.project.id, + path: result.project.pathWithNamespace, + } + : null, + }) + return {} + } + + const models: Record = {} + for (const m of result.models) { + if (!input.models[m.id]) { + models[m.id] = { + id: ModelID.make(m.id), + providerID: ProviderID.make("gitlab"), + name: `Agent Platform (${m.name})`, + family: "", + api: { + id: m.id, + url: instanceUrl, + npm: "gitlab-ai-provider", }, - output: { - text: true, - audio: false, - image: false, - video: false, - pdf: false, + status: "active", + headers: {}, + options: { workflowRef: m.ref }, + cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, + limit: { context: m.context, output: m.output }, + capabilities: { + temperature: false, + reasoning: true, + attachment: true, + toolcall: true, + input: { + text: true, + audio: false, + image: true, + video: false, + pdf: true, + }, + output: { + text: true, + audio: false, + image: false, + video: false, + pdf: false, + }, + interleaved: false, }, - interleaved: false, - }, - release_date: "", - variants: {}, + release_date: "", + variants: {}, + } } } - } - log.info("gitlab model discovery complete", { - count: Object.keys(models).length, - models: Object.keys(models), - }) - return models - } catch (e) { - log.warn("gitlab model discovery failed", { error: e }) - return {} - } - }, - } - }, - "cloudflare-workers-ai": async (input) => { - const accountId = Env.get("CLOUDFLARE_ACCOUNT_ID") - if (!accountId) return { autoload: false } - - const apiKey = await iife(async () => { - const envToken = Env.get("CLOUDFLARE_API_KEY") - if (envToken) return envToken - const auth = await Auth.get(input.id) - if (auth?.type === "api") return auth.key - return undefined - }) + log.info("gitlab model discovery complete", { + count: Object.keys(models).length, + models: Object.keys(models), + }) + return models + } catch (e) { + log.warn("gitlab model discovery failed", { error: e }) + return {} + } + }, + } + }), + "cloudflare-workers-ai": Effect.fnUntraced(function* (input: Info) { + const accountId = Env.get("CLOUDFLARE_ACCOUNT_ID") + if (!accountId) return { autoload: false } + + const apiKey = yield* Effect.gen(function* () { + const envToken = Env.get("CLOUDFLARE_API_KEY") + if (envToken) return envToken + const auth = yield* dep.auth(input.id) + if (auth?.type === "api") return auth.key + return undefined + }) - return { - autoload: !!apiKey, - options: { - apiKey, - headers: { - "User-Agent": `opencode/${Installation.VERSION} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`, + return { + autoload: !!apiKey, + options: { + apiKey, + headers: { + "User-Agent": `opencode/${Installation.VERSION} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`, + }, }, - }, - async getModel(sdk: any, modelID: string) { - return sdk.languageModel(modelID) - }, - vars(_options) { - return { - CLOUDFLARE_ACCOUNT_ID: accountId, - } - }, - } - }, - "cloudflare-ai-gateway": async (input) => { - const accountId = Env.get("CLOUDFLARE_ACCOUNT_ID") - const gateway = Env.get("CLOUDFLARE_GATEWAY_ID") - - if (!accountId || !gateway) return { autoload: false } - - // Get API token from env or auth - required for authenticated gateways - const apiToken = await (async () => { - const envToken = Env.get("CLOUDFLARE_API_TOKEN") || Env.get("CF_AIG_TOKEN") - if (envToken) return envToken - const auth = await Auth.get(input.id) - if (auth?.type === "api") return auth.key - return undefined - })() + async getModel(sdk: any, modelID: string) { + return sdk.languageModel(modelID) + }, + vars(_options) { + return { + CLOUDFLARE_ACCOUNT_ID: accountId, + } + }, + } + }), + "cloudflare-ai-gateway": Effect.fnUntraced(function* (input: Info) { + const accountId = Env.get("CLOUDFLARE_ACCOUNT_ID") + const gateway = Env.get("CLOUDFLARE_GATEWAY_ID") + + if (!accountId || !gateway) return { autoload: false } + + // Get API token from env or auth - required for authenticated gateways + const apiToken = yield* Effect.gen(function* () { + const envToken = Env.get("CLOUDFLARE_API_TOKEN") || Env.get("CF_AIG_TOKEN") + if (envToken) return envToken + const auth = yield* dep.auth(input.id) + if (auth?.type === "api") return auth.key + return undefined + }) - if (!apiToken) { - throw new Error( - "CLOUDFLARE_API_TOKEN (or CF_AIG_TOKEN) is required for Cloudflare AI Gateway. " + - "Set it via environment variable or run `opencode auth cloudflare-ai-gateway`.", - ) - } + if (!apiToken) { + throw new Error( + "CLOUDFLARE_API_TOKEN (or CF_AIG_TOKEN) is required for Cloudflare AI Gateway. " + + "Set it via environment variable or run `opencode auth cloudflare-ai-gateway`.", + ) + } - // Use official ai-gateway-provider package (v2.x for AI SDK v5 compatibility) - const { createAiGateway } = await import("ai-gateway-provider") - const { createUnified } = await import("ai-gateway-provider/providers/unified") + // Use official ai-gateway-provider package (v2.x for AI SDK v5 compatibility) + const { createAiGateway } = yield* Effect.promise(() => import("ai-gateway-provider")) + const { createUnified } = yield* Effect.promise(() => import("ai-gateway-provider/providers/unified")) - const metadata = iife(() => { - if (input.options?.metadata) return input.options.metadata - try { - return JSON.parse(input.options?.headers?.["cf-aig-metadata"]) - } catch { - return undefined + const metadata = iife(() => { + if (input.options?.metadata) return input.options.metadata + try { + return JSON.parse(input.options?.headers?.["cf-aig-metadata"]) + } catch { + return undefined + } + }) + const opts = { + metadata, + cacheTtl: input.options?.cacheTtl, + cacheKey: input.options?.cacheKey, + skipCache: input.options?.skipCache, + collectLog: input.options?.collectLog, + headers: { + "User-Agent": `opencode/${Installation.VERSION} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`, + }, } - }) - const opts = { - metadata, - cacheTtl: input.options?.cacheTtl, - cacheKey: input.options?.cacheKey, - skipCache: input.options?.skipCache, - collectLog: input.options?.collectLog, - headers: { - "User-Agent": `opencode/${Installation.VERSION} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`, - }, - } - const aigateway = createAiGateway({ - accountId, - gateway, - apiKey: apiToken, - ...(Object.values(opts).some((v) => v !== undefined) ? { options: opts } : {}), - }) - const unified = createUnified() + const aigateway = createAiGateway({ + accountId, + gateway, + apiKey: apiToken, + ...(Object.values(opts).some((v) => v !== undefined) ? { options: opts } : {}), + }) + const unified = createUnified() - return { - autoload: true, - async getModel(_sdk: any, modelID: string, _options?: Record) { - // Model IDs use Unified API format: provider/model (e.g., "anthropic/claude-sonnet-4-5") - return aigateway(unified(modelID)) - }, - options: {}, - } - }, - cerebras: async () => { - return { - autoload: false, - options: { - headers: { - "X-Cerebras-3rd-Party-Integration": "opencode", + return { + autoload: true, + async getModel(_sdk: any, modelID: string, _options?: Record) { + // Model IDs use Unified API format: provider/model (e.g., "anthropic/claude-sonnet-4-5") + return aigateway(unified(modelID)) }, - }, - } - }, - kilo: async () => { - return { - autoload: false, - options: { - headers: { - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", + options: {}, + } + }), + cerebras: () => + Effect.succeed({ + autoload: false, + options: { + headers: { + "X-Cerebras-3rd-Party-Integration": "opencode", + }, }, - }, - } - }, + }), + kilo: () => + Effect.succeed({ + autoload: false, + options: { + headers: { + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "opencode", + }, + }, + }), + } } export const Model = z @@ -989,15 +986,6 @@ export namespace Provider { const modelsDev = yield* Effect.promise(() => ModelsDev.get()) const database = mapValues(modelsDev, fromModelsDevProvider) - const disabled = new Set(cfg.disabled_providers ?? []) - const enabled = cfg.enabled_providers ? new Set(cfg.enabled_providers) : null - - function isProviderAllowed(providerID: ProviderID): boolean { - if (enabled && !enabled.has(providerID)) return false - if (disabled.has(providerID)) return false - return true - } - const providers: Record = {} as Record const languages = new Map() const modelLoaders: { @@ -1010,11 +998,13 @@ export namespace Provider { const discoveryLoaders: { [providerID: string]: CustomDiscoverModels } = {} + const dep = { + auth: (id: string) => auth.get(id).pipe(Effect.orDie), + config: () => config.get(), + } log.info("init") - const configProviders = Object.entries(cfg.provider ?? {}) - function mergeProvider(providerID: ProviderID, provider: Partial) { const existing = providers[providerID] if (existing) { @@ -1028,6 +1018,20 @@ export namespace Provider { providers[providerID] = mergeDeep(match, provider) } + // load plugins first so config() hook runs before reading cfg.provider + const plugins = yield* plugin.list() + + // now read config providers - includes any modifications from plugin config() hook + const configProviders = Object.entries(cfg.provider ?? {}) + const disabled = new Set(cfg.disabled_providers ?? []) + const enabled = cfg.enabled_providers ? new Set(cfg.enabled_providers) : null + + function isProviderAllowed(providerID: ProviderID): boolean { + if (enabled && !enabled.has(providerID)) return false + if (disabled.has(providerID)) return false + return true + } + // extend database from config for (const [providerID, provider] of configProviders) { const existing = database[providerID] @@ -1144,25 +1148,28 @@ export namespace Provider { } } - const plugins = yield* plugin.list() + // plugin auth loader - database now has entries for config providers for (const plugin of plugins) { if (!plugin.auth) continue const providerID = ProviderID.make(plugin.auth.provider) if (disabled.has(providerID)) continue - const pluginAuth = yield* auth.get(providerID).pipe(Effect.orDie) - if (!pluginAuth) continue + const stored = yield* auth.get(providerID).pipe(Effect.orDie) + if (!stored) continue if (!plugin.auth.loader) continue const options = yield* Effect.promise(() => - plugin.auth!.loader!(() => Auth.get(providerID) as any, database[plugin.auth!.provider]), + plugin.auth!.loader!( + () => Effect.runPromise(auth.get(providerID).pipe(Effect.orDie)) as any, + database[plugin.auth!.provider], + ), ) const opts = options ?? {} const patch: Partial = providers[providerID] ? { options: opts } : { source: "custom", options: opts } mergeProvider(providerID, patch) } - for (const [id, fn] of Object.entries(CUSTOM_LOADERS)) { + for (const [id, fn] of Object.entries(custom(dep))) { const providerID = ProviderID.make(id) if (disabled.has(providerID)) continue const data = database[providerID] @@ -1170,7 +1177,7 @@ export namespace Provider { log.error("Provider does not exist in model list " + providerID) continue } - const result = yield* Effect.promise(() => fn(data)) + const result = yield* fn(data) if (result && (result.autoload || providers[providerID])) { if (result.getModel) modelLoaders[providerID] = result.getModel if (result.vars) varsLoaders[providerID] = result.vars @@ -1183,7 +1190,7 @@ export namespace Provider { } } - // load config + // load config - re-apply with updated data for (const [id, provider] of configProviders) { const providerID = ProviderID.make(id) const partial: Partial = { source: "config" } diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 72ba9dba5a5c..88e9ea64c9de 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1,12 +1,22 @@ import { test, expect } from "bun:test" +import { mkdir, unlink } from "fs/promises" import path from "path" import { tmpdir } from "../fixture/fixture" +import { Global } from "../../src/global" import { Instance } from "../../src/project/instance" +import { Plugin } from "../../src/plugin/index" import { Provider } from "../../src/provider/provider" import { ProviderID, ModelID } from "../../src/provider/schema" +import { Filesystem } from "../../src/util/filesystem" import { Env } from "../../src/env" +function paid(providers: Awaited>) { + const item = providers[ProviderID.make("opencode")] + expect(item).toBeDefined() + return Object.values(item.models).filter((model) => model.cost.input > 0).length +} + test("provider loaded from env variable", async () => { await using tmp = await tmpdir({ init: async (dir) => { @@ -2282,3 +2292,203 @@ test("cloudflare-ai-gateway forwards config metadata options", async () => { }, }) }) + +test("plugin config providers persist after instance dispose", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + const root = path.join(dir, ".opencode", "plugin") + await mkdir(root, { recursive: true }) + await Bun.write( + path.join(root, "demo-provider.ts"), + [ + "export default {", + ' id: "demo.plugin-provider",', + " server: async () => ({", + " async config(cfg) {", + " cfg.provider ??= {}", + " cfg.provider.demo = {", + ' name: "Demo Provider",', + ' npm: "@ai-sdk/openai-compatible",', + ' api: "https://example.com/v1",', + " models: {", + " chat: {", + ' name: "Demo Chat",', + " tool_call: true,", + " limit: { context: 128000, output: 4096 },", + " },", + " },", + " }", + " },", + " }),", + "}", + "", + ].join("\n"), + ) + }, + }) + + const first = await Instance.provide({ + directory: tmp.path, + fn: async () => { + await Plugin.init() + return Provider.list() + }, + }) + expect(first[ProviderID.make("demo")]).toBeDefined() + expect(first[ProviderID.make("demo")].models[ModelID.make("chat")]).toBeDefined() + + await Instance.disposeAll() + + const second = await Instance.provide({ + directory: tmp.path, + fn: async () => Provider.list(), + }) + expect(second[ProviderID.make("demo")]).toBeDefined() + expect(second[ProviderID.make("demo")].models[ModelID.make("chat")]).toBeDefined() +}) + +test("plugin config enabled and disabled providers are honored", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + const root = path.join(dir, ".opencode", "plugin") + await mkdir(root, { recursive: true }) + await Bun.write( + path.join(root, "provider-filter.ts"), + [ + "export default {", + ' id: "demo.provider-filter",', + " server: async () => ({", + " async config(cfg) {", + ' cfg.enabled_providers = ["anthropic", "openai"]', + ' cfg.disabled_providers = ["openai"]', + " },", + " }),", + "}", + "", + ].join("\n"), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + init: async () => { + Env.set("ANTHROPIC_API_KEY", "test-anthropic-key") + Env.set("OPENAI_API_KEY", "test-openai-key") + }, + fn: async () => { + const providers = await Provider.list() + expect(providers[ProviderID.anthropic]).toBeDefined() + expect(providers[ProviderID.openai]).toBeUndefined() + }, + }) +}) + +test("opencode loader keeps paid models when config apiKey is present", async () => { + await using base = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + + const none = await Instance.provide({ + directory: base.path, + fn: async () => paid(await Provider.list()), + }) + + await using keyed = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + opencode: { + options: { + apiKey: "test-key", + }, + }, + }, + }), + ) + }, + }) + + const keyedCount = await Instance.provide({ + directory: keyed.path, + fn: async () => paid(await Provider.list()), + }) + + expect(none).toBe(0) + expect(keyedCount).toBeGreaterThan(0) +}) + +test("opencode loader keeps paid models when auth exists", async () => { + await using base = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + + const none = await Instance.provide({ + directory: base.path, + fn: async () => paid(await Provider.list()), + }) + + await using keyed = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + + const authPath = path.join(Global.Path.data, "auth.json") + let prev: string | undefined + + try { + prev = await Filesystem.readText(authPath) + } catch {} + + try { + await Filesystem.write( + authPath, + JSON.stringify({ + opencode: { + type: "api", + key: "test-key", + }, + }), + ) + + const keyedCount = await Instance.provide({ + directory: keyed.path, + fn: async () => paid(await Provider.list()), + }) + + expect(none).toBe(0) + expect(keyedCount).toBeGreaterThan(0) + } finally { + if (prev !== undefined) { + await Filesystem.write(authPath, prev) + } + if (prev === undefined) { + try { + await unlink(authPath) + } catch {} + } + } +}) From 9fea09945906a4e5adca4a83e27dc96fee8b7c2a Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 3 Apr 2026 21:05:23 -0400 Subject: [PATCH 073/616] perf(opencode): batch snapshot diffFull blob reads (#20752) Co-authored-by: Nate Williams <50088025+natewill@users.noreply.github.com> --- packages/opencode/src/snapshot/index.ts | 201 ++++++++++++++++-- .../opencode/test/snapshot/snapshot.test.ts | 92 ++++++++ 2 files changed, 270 insertions(+), 23 deletions(-) diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 7c952bc54db9..2db67695ff07 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -437,6 +437,146 @@ export namespace Snapshot { const diffFull = Effect.fnUntraced(function* (from: string, to: string) { return yield* locked( Effect.gen(function* () { + type Row = { + file: string + status: "added" | "deleted" | "modified" + binary: boolean + additions: number + deletions: number + } + + type Ref = { + file: string + side: "before" | "after" + ref: string + } + + const show = Effect.fnUntraced(function* (row: Row) { + if (row.binary) return ["", ""] + if (row.status === "added") { + return [ + "", + yield* git([...cfg, ...args(["show", `${to}:${row.file}`])]).pipe( + Effect.map((item) => item.text), + ), + ] + } + if (row.status === "deleted") { + return [ + yield* git([...cfg, ...args(["show", `${from}:${row.file}`])]).pipe( + Effect.map((item) => item.text), + ), + "", + ] + } + return yield* Effect.all( + [ + git([...cfg, ...args(["show", `${from}:${row.file}`])]).pipe(Effect.map((item) => item.text)), + git([...cfg, ...args(["show", `${to}:${row.file}`])]).pipe(Effect.map((item) => item.text)), + ], + { concurrency: 2 }, + ) + }) + + const load = Effect.fnUntraced( + function* (rows: Row[]) { + const refs = rows.flatMap((row) => { + if (row.binary) return [] + if (row.status === "added") + return [{ file: row.file, side: "after", ref: `${to}:${row.file}` } satisfies Ref] + if (row.status === "deleted") { + return [{ file: row.file, side: "before", ref: `${from}:${row.file}` } satisfies Ref] + } + return [ + { file: row.file, side: "before", ref: `${from}:${row.file}` } satisfies Ref, + { file: row.file, side: "after", ref: `${to}:${row.file}` } satisfies Ref, + ] + }) + if (!refs.length) return new Map() + + const proc = ChildProcess.make("git", [...cfg, ...args(["cat-file", "--batch"])], { + cwd: state.directory, + extendEnv: true, + stdin: Stream.make(new TextEncoder().encode(refs.map((item) => item.ref).join("\n") + "\n")), + }) + const handle = yield* spawner.spawn(proc) + const [out, err] = yield* Effect.all( + [Stream.mkUint8Array(handle.stdout), Stream.mkString(Stream.decodeText(handle.stderr))], + { concurrency: 2 }, + ) + const code = yield* handle.exitCode + if (code !== 0) { + log.info("git cat-file --batch failed during snapshot diff, falling back to per-file git show", { + stderr: err, + refs: refs.length, + }) + return + } + + const fail = (msg: string, extra?: Record) => { + log.info(msg, { ...extra, refs: refs.length }) + return undefined + } + + const map = new Map() + const dec = new TextDecoder() + let i = 0 + // Parse the default `git cat-file --batch` stream: one header line, + // then exactly `size` bytes of blob content, then a trailing newline. + for (const ref of refs) { + let end = i + while (end < out.length && out[end] !== 10) end += 1 + if (end >= out.length) { + return fail( + "git cat-file --batch returned a truncated header during snapshot diff, falling back to per-file git show", + ) + } + + const head = dec.decode(out.slice(i, end)) + i = end + 1 + const hit = map.get(ref.file) ?? { before: "", after: "" } + if (head.endsWith(" missing")) { + map.set(ref.file, hit) + continue + } + + const match = head.match(/^[0-9a-f]+ blob (\d+)$/) + if (!match) { + return fail( + "git cat-file --batch returned an unexpected header during snapshot diff, falling back to per-file git show", + { head }, + ) + } + + const size = Number(match[1]) + if (!Number.isInteger(size) || size < 0 || i + size >= out.length || out[i + size] !== 10) { + return fail( + "git cat-file --batch returned truncated content during snapshot diff, falling back to per-file git show", + { head }, + ) + } + + const text = dec.decode(out.slice(i, i + size)) + if (ref.side === "before") hit.before = text + if (ref.side === "after") hit.after = text + map.set(ref.file, hit) + i += size + 1 + } + + if (i !== out.length) { + return fail( + "git cat-file --batch returned trailing data during snapshot diff, falling back to per-file git show", + ) + } + + return map + }, + Effect.scoped, + Effect.catch(() => + Effect.succeed | undefined>(undefined), + ), + ) + const result: Snapshot.FileDiff[] = [] const status = new Map() @@ -459,30 +599,45 @@ export namespace Snapshot { }, ) - for (const line of numstat.text.trim().split("\n")) { - if (!line) continue - const [adds, dels, file] = line.split("\t") - if (!file) continue - const binary = adds === "-" && dels === "-" - const [before, after] = binary - ? ["", ""] - : yield* Effect.all( - [ - git([...cfg, ...args(["show", `${from}:${file}`])]).pipe(Effect.map((item) => item.text)), - git([...cfg, ...args(["show", `${to}:${file}`])]).pipe(Effect.map((item) => item.text)), - ], - { concurrency: 2 }, - ) - const additions = binary ? 0 : parseInt(adds) - const deletions = binary ? 0 : parseInt(dels) - result.push({ - file, - before, - after, - additions: Number.isFinite(additions) ? additions : 0, - deletions: Number.isFinite(deletions) ? deletions : 0, - status: status.get(file) ?? "modified", + const rows = numstat.text + .trim() + .split("\n") + .filter(Boolean) + .flatMap((line) => { + const [adds, dels, file] = line.split("\t") + if (!file) return [] + const binary = adds === "-" && dels === "-" + const additions = binary ? 0 : parseInt(adds) + const deletions = binary ? 0 : parseInt(dels) + return [ + { + file, + status: status.get(file) ?? "modified", + binary, + additions: Number.isFinite(additions) ? additions : 0, + deletions: Number.isFinite(deletions) ? deletions : 0, + } satisfies Row, + ] }) + const step = 100 + + // Keep batches bounded so a large diff does not buffer every blob at once. + for (let i = 0; i < rows.length; i += step) { + const run = rows.slice(i, i + step) + const text = yield* load(run) + + for (const row of run) { + const hit = text?.get(row.file) ?? { before: "", after: "" } + const [before, after] = row.binary ? ["", ""] : text ? [hit.before, hit.after] : yield* show(row) + result.push({ + file: row.file, + before, + after, + additions: row.additions, + deletions: row.deletions, + status: row.status, + }) + } } return result diff --git a/packages/opencode/test/snapshot/snapshot.test.ts b/packages/opencode/test/snapshot/snapshot.test.ts index 8dc80721de4e..0cd9366a533c 100644 --- a/packages/opencode/test/snapshot/snapshot.test.ts +++ b/packages/opencode/test/snapshot/snapshot.test.ts @@ -982,6 +982,98 @@ test("diffFull with new file additions", async () => { }) }) +test("diffFull with a large interleaved mixed diff", async () => { + await using tmp = await bootstrap() + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const ids = Array.from({ length: 60 }, (_, i) => i.toString().padStart(3, "0")) + const mod = ids.map((id) => fwd(tmp.path, "mix", `${id}-mod.txt`)) + const del = ids.map((id) => fwd(tmp.path, "mix", `${id}-del.txt`)) + const add = ids.map((id) => fwd(tmp.path, "mix", `${id}-add.txt`)) + const bin = ids.map((id) => fwd(tmp.path, "mix", `${id}-bin.bin`)) + + await $`mkdir -p ${tmp.path}/mix`.quiet() + await Promise.all([ + ...mod.map((file, i) => Filesystem.write(file, `before-${ids[i]}-é\n🙂\nline`)), + ...del.map((file, i) => Filesystem.write(file, `gone-${ids[i]}\n你好`)), + ...bin.map((file, i) => Filesystem.write(file, new Uint8Array([0, i, 255, i % 251]))), + ]) + + const before = await Snapshot.track() + expect(before).toBeTruthy() + + await Promise.all([ + ...mod.map((file, i) => Filesystem.write(file, `after-${ids[i]}-é\n🚀\nline`)), + ...add.map((file, i) => Filesystem.write(file, `new-${ids[i]}\nこんにちは`)), + ...bin.map((file, i) => Filesystem.write(file, new Uint8Array([9, i, 8, i % 251]))), + ...del.map((file) => fs.rm(file)), + ]) + + const after = await Snapshot.track() + expect(after).toBeTruthy() + + const diffs = await Snapshot.diffFull(before!, after!) + expect(diffs).toHaveLength(ids.length * 4) + + const map = new Map(diffs.map((item) => [item.file, item])) + for (let i = 0; i < ids.length; i++) { + const m = map.get(fwd("mix", `${ids[i]}-mod.txt`)) + expect(m).toBeDefined() + expect(m!.before).toBe(`before-${ids[i]}-é\n🙂\nline`) + expect(m!.after).toBe(`after-${ids[i]}-é\n🚀\nline`) + expect(m!.status).toBe("modified") + + const d = map.get(fwd("mix", `${ids[i]}-del.txt`)) + expect(d).toBeDefined() + expect(d!.before).toBe(`gone-${ids[i]}\n你好`) + expect(d!.after).toBe("") + expect(d!.status).toBe("deleted") + + const a = map.get(fwd("mix", `${ids[i]}-add.txt`)) + expect(a).toBeDefined() + expect(a!.before).toBe("") + expect(a!.after).toBe(`new-${ids[i]}\nこんにちは`) + expect(a!.status).toBe("added") + + const b = map.get(fwd("mix", `${ids[i]}-bin.bin`)) + expect(b).toBeDefined() + expect(b!.before).toBe("") + expect(b!.after).toBe("") + expect(b!.additions).toBe(0) + expect(b!.deletions).toBe(0) + expect(b!.status).toBe("modified") + } + }, + }) +}) + +test("diffFull preserves git diff order across batch boundaries", async () => { + await using tmp = await bootstrap() + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const ids = Array.from({ length: 140 }, (_, i) => i.toString().padStart(3, "0")) + + await $`mkdir -p ${tmp.path}/order`.quiet() + await Promise.all(ids.map((id) => Filesystem.write(`${tmp.path}/order/${id}.txt`, `before-${id}`))) + + const before = await Snapshot.track() + expect(before).toBeTruthy() + + await Promise.all(ids.map((id) => Filesystem.write(`${tmp.path}/order/${id}.txt`, `after-${id}`))) + + const after = await Snapshot.track() + expect(after).toBeTruthy() + + const expected = ids.map((id) => `order/${id}.txt`) + + const diffs = await Snapshot.diffFull(before!, after!) + expect(diffs.map((item) => item.file)).toEqual(expected) + }, + }) +}) + test("diffFull with file modifications", async () => { await using tmp = await bootstrap() await Instance.provide({ From 573cfb08ea6758304f34450c8947149d9a9600fe Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 3 Apr 2026 22:47:20 -0400 Subject: [PATCH 074/616] fix(ci): create JUnit output dirs before tests (#20959) --- .github/workflows/test.yml | 1 + packages/app/package.json | 2 +- packages/opencode/package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 119d652ae965..70a8477fb51f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,6 +75,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: unit-${{ matrix.settings.name }}-${{ github.run_attempt }} + include-hidden-files: true if-no-files-found: ignore retention-days: 7 path: packages/*/.artifacts/unit/junit.xml diff --git a/packages/app/package.json b/packages/app/package.json index 62749e209a2a..ab72a927ad0c 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -15,7 +15,7 @@ "build": "vite build", "serve": "vite preview", "test": "bun run test:unit", - "test:ci": "bun test --preload ./happydom.ts ./src --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml", + "test:ci": "mkdir -p .artifacts/unit && bun test --preload ./happydom.ts ./src --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml", "test:unit": "bun test --preload ./happydom.ts ./src", "test:unit:watch": "bun test --watch --preload ./happydom.ts ./src", "test:e2e": "playwright test", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 54b40b31b882..a7c92d779986 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -9,7 +9,7 @@ "prepare": "effect-language-service patch || true", "typecheck": "tsgo --noEmit", "test": "bun test --timeout 30000", - "test:ci": "bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml", + "test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml", "build": "bun run script/build.ts", "upgrade-opentui": "bun run script/upgrade-opentui.ts", "dev": "bun run --conditions=browser ./src/index.ts", From e6f60986b3b99f46fff78eb5d6464a20a4834fc8 Mon Sep 17 00:00:00 2001 From: opencode Date: Sat, 4 Apr 2026 18:32:17 +0000 Subject: [PATCH 075/616] release: v1.3.14 --- bun.lock | 32 +++++++++++++------------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop-electron/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++----- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/util/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index f103c9a33392..d3cbeec9ab54 100644 --- a/bun.lock +++ b/bun.lock @@ -25,7 +25,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -79,7 +79,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -113,7 +113,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -140,7 +140,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -164,7 +164,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -188,7 +188,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -221,7 +221,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -253,7 +253,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -282,7 +282,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -298,7 +298,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.3.13", + "version": "1.3.14", "bin": { "opencode": "./bin/opencode", }, @@ -460,7 +460,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -494,7 +494,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "cross-spawn": "catalog:", }, @@ -509,7 +509,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -544,7 +544,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -592,7 +592,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "zod": "catalog:", }, @@ -603,7 +603,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index ab72a927ad0c..28438c8b478d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.3.13", + "version": "1.3.14", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index cea0e781750c..5e38840e9e1a 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.3.13", + "version": "1.3.14", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 3131e2e03e84..11c9daa14236 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.3.13", + "version": "1.3.14", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index ef019ff41b9f..adad91313b08 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.3.13", + "version": "1.3.14", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index aee0b0fad125..b5352c6d2b0d 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index b875cdd2a637..26c4b8f8e5e3 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.3.13", + "version": "1.3.14", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 893617f87577..1fe44524e5fb 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.3.13", + "version": "1.3.14", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index cdcf31d6860e..136c9029ae68 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.3.13", + "version": "1.3.14", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 0a80ec8fed86..4283d0ed25ec 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.3.13" +version = "1.3.14" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/anomalyco/opencode" @@ -11,26 +11,26 @@ name = "OpenCode" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.13/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.14/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.13/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.14/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.13/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.14/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.13/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.14/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.13/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.14/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 81bd322efd44..1abb67cb1691 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.3.13", + "version": "1.3.14", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index a7c92d779986..a75a0e11d895 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.3.13", + "version": "1.3.14", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index e1cde0fd7e8e..0769195668e6 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.3.13", + "version": "1.3.14", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index ed4e47384844..7d36bd7d3714 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.3.13", + "version": "1.3.14", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/slack/package.json b/packages/slack/package.json index e297038530b2..26d27be7a4a0 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.3.13", + "version": "1.3.14", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 8c925753e837..d3f32ccde527 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.3.13", + "version": "1.3.14", "type": "module", "license": "MIT", "exports": { diff --git a/packages/util/package.json b/packages/util/package.json index 35e910dcafa6..9f2055917bbb 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.3.13", + "version": "1.3.14", "private": true, "type": "module", "license": "MIT", diff --git a/packages/web/package.json b/packages/web/package.json index 60082740bbef..eff4a1c1dd44 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.3.13", + "version": "1.3.14", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index e656181ad28c..bdcfa727ff26 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.3.13", + "version": "1.3.14", "publisher": "sst-dev", "repository": { "type": "git", From 6f35a260e783aace98b96e22b8685257e7b5cddc Mon Sep 17 00:00:00 2001 From: Yuxin Dong Date: Sun, 5 Apr 2026 02:30:57 +0800 Subject: [PATCH 076/616] refactor: remove redundant Kimi skill section (#20393) Co-authored-by: dongyuxin Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/opencode/src/session/prompt/kimi.txt | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/opencode/src/session/prompt/kimi.txt b/packages/opencode/src/session/prompt/kimi.txt index 5d90c61f2c18..beff6755f97d 100644 --- a/packages/opencode/src/session/prompt/kimi.txt +++ b/packages/opencode/src/session/prompt/kimi.txt @@ -82,25 +82,6 @@ If the `AGENTS.md` is empty or insufficient, you may check `README`/`README.md` If you modified any files/styles/structures/configurations/workflows/... mentioned in `AGENTS.md` files, you MUST update the corresponding `AGENTS.md` files to keep them up-to-date. -# Skills - -Skills are reusable, composable capabilities that enhance your abilities. Each skill is a self-contained directory with a `SKILL.md` file that contains instructions, examples, and/or reference material. - -## What are skills? - -Skills are modular extensions that provide: - -- Specialized knowledge: Domain-specific expertise (e.g., PDF processing, data analysis) -- Workflow patterns: Best practices for common tasks -- Tool integrations: Pre-configured tool chains for specific operations -- Reference material: Documentation, templates, and examples - -## How to use skills - -Identify the skills that are likely to be useful for the tasks you are currently working on, use the `skill` tool to load a skill for detailed instructions, guidelines, scripts and more. - -Only load skill details when needed to conserve the context window. - # Ultimate Reminders At any time, you should be HELPFUL, CONCISE, and ACCURATE. Be thorough in your actions — test what you build, verify what you change — not in your explanations. From 493c8e15e30c85ac047c505cc4110b300e87ce59 Mon Sep 17 00:00:00 2001 From: Dax Date: Sat, 4 Apr 2026 16:27:20 -0400 Subject: [PATCH 077/616] =?UTF-8?q?fix(npm):=20Arborist=20reify=20fails=20?= =?UTF-8?q?on=20compiled=20binary=20=E2=80=94=20Bun=20pre-resolves=20node-?= =?UTF-8?q?gyp=20path=20at=20build=20time=20(#21040)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/opencode/script/build.ts | 1 + packages/opencode/src/npm/index.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts index b104dd26774d..9c3d9bb5b7e4 100755 --- a/packages/opencode/script/build.ts +++ b/packages/opencode/script/build.ts @@ -209,6 +209,7 @@ for (const item of targets) { conditions: ["browser"], tsconfig: "./tsconfig.json", plugins: [plugin], + external: ["node-gyp"], compile: { autoloadBunfig: false, autoloadDotenv: false, diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index 9b913b327887..69bb2ca5284e 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -67,6 +67,7 @@ export namespace Npm { binLinks: true, progress: false, savePrefix: "", + ignoreScripts: true, }) const tree = await arborist.loadVirtual().catch(() => {}) if (tree) { @@ -106,6 +107,7 @@ export namespace Npm { binLinks: true, progress: false, savePrefix: "", + ignoreScripts: true, }) await arb.reify().catch(() => {}) } From 381ee9ff34e647edf3dad44bd6fb9482698d04d9 Mon Sep 17 00:00:00 2001 From: opencode Date: Sat, 4 Apr 2026 20:54:20 +0000 Subject: [PATCH 078/616] release: v1.3.15 --- bun.lock | 32 +++++++++++++------------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop-electron/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++----- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/util/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index d3cbeec9ab54..3550c67441cb 100644 --- a/bun.lock +++ b/bun.lock @@ -25,7 +25,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -79,7 +79,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -113,7 +113,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -140,7 +140,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -164,7 +164,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -188,7 +188,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -221,7 +221,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -253,7 +253,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -282,7 +282,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -298,7 +298,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.3.14", + "version": "1.3.15", "bin": { "opencode": "./bin/opencode", }, @@ -460,7 +460,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -494,7 +494,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "cross-spawn": "catalog:", }, @@ -509,7 +509,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -544,7 +544,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -592,7 +592,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "zod": "catalog:", }, @@ -603,7 +603,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 28438c8b478d..3d71ec1e7fc0 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.3.14", + "version": "1.3.15", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 5e38840e9e1a..6b52b23dc23d 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.3.14", + "version": "1.3.15", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 11c9daa14236..3a1fa330f614 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.3.14", + "version": "1.3.15", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index adad91313b08..b630e41abc11 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.3.14", + "version": "1.3.15", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index b5352c6d2b0d..ff81ab887395 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.3.14", + "version": "1.3.15", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 26c4b8f8e5e3..64af9910e2b0 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.3.14", + "version": "1.3.15", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 1fe44524e5fb..eb0c8034cdb0 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.3.14", + "version": "1.3.15", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 136c9029ae68..aee89a15047b 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.3.14", + "version": "1.3.15", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 4283d0ed25ec..dc4a944ba101 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.3.14" +version = "1.3.15" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/anomalyco/opencode" @@ -11,26 +11,26 @@ name = "OpenCode" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.14/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.15/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.14/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.15/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.14/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.15/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.14/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.15/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.14/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.15/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 1abb67cb1691..637adefec200 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.3.14", + "version": "1.3.15", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index a75a0e11d895..24f296e73f47 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.3.14", + "version": "1.3.15", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 0769195668e6..f4bd4e401c77 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.3.14", + "version": "1.3.15", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 7d36bd7d3714..04fb5194ad72 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.3.14", + "version": "1.3.15", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/slack/package.json b/packages/slack/package.json index 26d27be7a4a0..3de4f3b61507 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.3.14", + "version": "1.3.15", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index d3f32ccde527..16268d9c746c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.3.14", + "version": "1.3.15", "type": "module", "license": "MIT", "exports": { diff --git a/packages/util/package.json b/packages/util/package.json index 9f2055917bbb..7b3cf2159008 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.3.14", + "version": "1.3.15", "private": true, "type": "module", "license": "MIT", diff --git a/packages/web/package.json b/packages/web/package.json index eff4a1c1dd44..24bb2354c3b0 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.3.14", + "version": "1.3.15", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index bdcfa727ff26..36e9ec554015 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.3.14", + "version": "1.3.15", "publisher": "sst-dev", "repository": { "type": "git", From 1c8f81b2bd69241248c372d5229ed141e93a003b Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:29:53 -0700 Subject: [PATCH 079/616] fix: ensure reasoning tokens arent double counted when calculating usage (#21047) --- packages/opencode/src/session/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 41fad1a9d483..65032de96252 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -278,7 +278,7 @@ export namespace Session { const tokens = { total, input: adjustedInputTokens, - output: outputTokens, + output: outputTokens - reasoningTokens, reasoning: reasoningTokens, cache: { write: cacheWriteInputTokens, From 813651327854a4e37ab869e82730665d6e507010 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 4 Apr 2026 19:05:45 -0400 Subject: [PATCH 080/616] feat(tui): show console-managed providers (#20956) --- packages/opencode/src/account/index.ts | 55 +++++- packages/opencode/src/cli/cmd/tui/app.tsx | 14 ++ .../cmd/tui/component/dialog-console-org.tsx | 103 +++++++++++ .../cli/cmd/tui/component/dialog-model.tsx | 17 +- .../cli/cmd/tui/component/dialog-provider.tsx | 167 ++++++++++-------- .../cli/cmd/tui/component/prompt/index.tsx | 22 ++- .../opencode/src/cli/cmd/tui/context/sync.tsx | 18 +- .../src/cli/cmd/tui/ui/dialog-select.tsx | 14 +- .../src/cli/cmd/tui/util/provider-origin.ts | 20 +++ packages/opencode/src/config/config.ts | 45 +++-- packages/opencode/src/config/console-state.ts | 13 ++ .../src/server/routes/experimental.ts | 99 +++++++++++ packages/opencode/test/config/config.test.ts | 16 ++ packages/sdk/js/src/v2/gen/sdk.gen.ts | 151 +++++++++++++--- packages/sdk/js/src/v2/gen/types.gen.ts | 74 ++++++++ 15 files changed, 706 insertions(+), 122 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/component/dialog-console-org.tsx create mode 100644 packages/opencode/src/cli/cmd/tui/util/provider-origin.ts create mode 100644 packages/opencode/src/config/console-state.ts diff --git a/packages/opencode/src/account/index.ts b/packages/opencode/src/account/index.ts index 2a8d35bfa8de..a1bb614ce41f 100644 --- a/packages/opencode/src/account/index.ts +++ b/packages/opencode/src/account/index.ts @@ -52,6 +52,11 @@ export type AccountOrgs = { orgs: readonly Org[] } +export type ActiveOrg = { + account: Info + org: Org +} + class RemoteConfig extends Schema.Class("RemoteConfig")({ config: Schema.Record(Schema.String, Schema.Json), }) {} @@ -137,6 +142,7 @@ const mapAccountServiceError = export namespace Account { export interface Interface { readonly active: () => Effect.Effect, AccountError> + readonly activeOrg: () => Effect.Effect, AccountError> readonly list: () => Effect.Effect readonly orgsByAccount: () => Effect.Effect readonly remove: (accountID: AccountID) => Effect.Effect @@ -279,19 +285,31 @@ export namespace Account { resolveAccess(accountID).pipe(Effect.map(Option.map((r) => r.accessToken))), ) + const activeOrg = Effect.fn("Account.activeOrg")(function* () { + const activeAccount = yield* repo.active() + if (Option.isNone(activeAccount)) return Option.none() + + const account = activeAccount.value + if (!account.active_org_id) return Option.none() + + const accountOrgs = yield* orgs(account.id) + const org = accountOrgs.find((item) => item.id === account.active_org_id) + if (!org) return Option.none() + + return Option.some({ account, org }) + }) + const orgsByAccount = Effect.fn("Account.orgsByAccount")(function* () { const accounts = yield* repo.list() - const [errors, results] = yield* Effect.partition( + return yield* Effect.forEach( accounts, - (account) => orgs(account.id).pipe(Effect.map((orgs) => ({ account, orgs }))), + (account) => + orgs(account.id).pipe( + Effect.catch(() => Effect.succeed([] as readonly Org[])), + Effect.map((orgs) => ({ account, orgs })), + ), { concurrency: 3 }, ) - for (const error of errors) { - yield* Effect.logWarning("failed to fetch orgs for account").pipe( - Effect.annotateLogs({ error: String(error) }), - ) - } - return results }) const orgs = Effect.fn("Account.orgs")(function* (accountID: AccountID) { @@ -396,6 +414,7 @@ export namespace Account { return Service.of({ active: repo.active, + activeOrg, list: repo.list, orgsByAccount, remove: repo.remove, @@ -417,6 +436,26 @@ export namespace Account { return Option.getOrUndefined(await runPromise((service) => service.active())) } + export async function list(): Promise { + return runPromise((service) => service.list()) + } + + export async function activeOrg(): Promise { + return Option.getOrUndefined(await runPromise((service) => service.activeOrg())) + } + + export async function orgsByAccount(): Promise { + return runPromise((service) => service.orgsByAccount()) + } + + export async function orgs(accountID: AccountID): Promise { + return runPromise((service) => service.orgs(accountID)) + } + + export async function switchOrg(accountID: AccountID, orgID: OrgID) { + return runPromise((service) => service.use(accountID, Option.some(orgID))) + } + export async function token(accountID: AccountID): Promise { const t = await runPromise((service) => service.token(accountID)) return Option.getOrUndefined(t) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 93d1fc19ae2b..8ce7382929af 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -36,6 +36,7 @@ import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command import { DialogAgent } from "@tui/component/dialog-agent" import { DialogSessionList } from "@tui/component/dialog-session-list" import { DialogWorkspaceList } from "@tui/component/dialog-workspace-list" +import { DialogConsoleOrg } from "@tui/component/dialog-console-org" import { KeybindProvider, useKeybind } from "@tui/context/keybind" import { ThemeProvider, useTheme } from "@tui/context/theme" import { Home } from "@tui/routes/home" @@ -629,6 +630,19 @@ function App(props: { onSnapshot?: () => Promise }) { }, category: "Provider", }, + { + title: "Switch org", + value: "console.org.switch", + suggested: Boolean(sync.data.console_state.activeOrgName), + slash: { + name: "org", + aliases: ["orgs", "switch-org"], + }, + onSelect: () => { + dialog.replace(() => ) + }, + category: "Provider", + }, { title: "View status", keybind: "status_view", diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-console-org.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-console-org.tsx new file mode 100644 index 000000000000..eaf3450196e7 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-console-org.tsx @@ -0,0 +1,103 @@ +import { createResource, createMemo } from "solid-js" +import { DialogSelect } from "@tui/ui/dialog-select" +import { useSDK } from "@tui/context/sdk" +import { useDialog } from "@tui/ui/dialog" +import { useToast } from "@tui/ui/toast" +import { useTheme } from "@tui/context/theme" +import type { ExperimentalConsoleListOrgsResponse } from "@opencode-ai/sdk/v2" + +type OrgOption = ExperimentalConsoleListOrgsResponse["orgs"][number] + +const accountHost = (url: string) => { + try { + return new URL(url).host + } catch { + return url + } +} + +const accountLabel = (item: Pick) => + `${item.accountEmail} ${accountHost(item.accountUrl)}` + +export function DialogConsoleOrg() { + const sdk = useSDK() + const dialog = useDialog() + const toast = useToast() + const { theme } = useTheme() + + const [orgs] = createResource(async () => { + const result = await sdk.client.experimental.console.listOrgs({}, { throwOnError: true }) + return result.data?.orgs ?? [] + }) + + const current = createMemo(() => orgs()?.find((item) => item.active)) + + const options = createMemo(() => { + const listed = orgs() + if (listed === undefined) { + return [ + { + title: "Loading orgs...", + value: "loading", + onSelect: () => {}, + }, + ] + } + + if (listed.length === 0) { + return [ + { + title: "No orgs found", + value: "empty", + onSelect: () => {}, + }, + ] + } + + return listed + .toSorted((a, b) => { + const activeAccountA = a.active ? 0 : 1 + const activeAccountB = b.active ? 0 : 1 + if (activeAccountA !== activeAccountB) return activeAccountA - activeAccountB + + const accountCompare = accountLabel(a).localeCompare(accountLabel(b)) + if (accountCompare !== 0) return accountCompare + + return a.orgName.localeCompare(b.orgName) + }) + .map((item) => ({ + title: item.orgName, + value: item, + category: accountLabel(item), + categoryView: ( + + {item.accountEmail} + {accountHost(item.accountUrl)} + + ), + onSelect: async () => { + if (item.active) { + dialog.clear() + return + } + + await sdk.client.experimental.console.switchOrg( + { + accountID: item.accountID, + orgID: item.orgID, + }, + { throwOnError: true }, + ) + + await sdk.client.instance.dispose() + toast.show({ + message: `Switched to ${item.orgName}`, + variant: "info", + }) + dialog.clear() + }, + })) + }) + + return title="Switch org" options={options()} current={current()} /> +} diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx index 549165f51a0d..1fd1c130c6d3 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx @@ -8,6 +8,7 @@ import { createDialogProviderOptions, DialogProvider } from "./dialog-provider" import { DialogVariant } from "./dialog-variant" import { useKeybind } from "../context/keybind" import * as fuzzysort from "fuzzysort" +import { consoleManagedProviderLabel } from "@tui/util/provider-origin" export function useConnected() { const sync = useSync() @@ -46,7 +47,11 @@ export function DialogModel(props: { providerID?: string }) { key: item, value: { providerID: provider.id, modelID: model.id }, title: model.name ?? item.modelID, - description: provider.name, + description: consoleManagedProviderLabel( + sync.data.console_state.consoleManagedProviders, + provider.id, + provider.name, + ), category, disabled: provider.id === "opencode" && model.id.includes("-nano"), footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined, @@ -84,7 +89,9 @@ export function DialogModel(props: { providerID?: string }) { description: favorites.some((item) => item.providerID === provider.id && item.modelID === model) ? "(Favorite)" : undefined, - category: connected() ? provider.name : undefined, + category: connected() + ? consoleManagedProviderLabel(sync.data.console_state.consoleManagedProviders, provider.id, provider.name) + : undefined, disabled: provider.id === "opencode" && model.includes("-nano"), footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined, onSelect() { @@ -132,7 +139,11 @@ export function DialogModel(props: { providerID?: string }) { props.providerID ? sync.data.provider.find((x) => x.id === props.providerID) : null, ) - const title = createMemo(() => provider()?.name ?? "Select model") + const title = createMemo(() => { + const value = provider() + if (!value) return "Select model" + return consoleManagedProviderLabel(sync.data.console_state.consoleManagedProviders, value.id, value.name) + }) function onSelect(providerID: string, modelID: string) { local.model.set({ providerID, modelID }, { recent: true }) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx index 635ed71f5b34..8add73dd6e45 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx @@ -13,6 +13,7 @@ import { DialogModel } from "./dialog-model" import { useKeyboard } from "@opentui/solid" import { Clipboard } from "@tui/util/clipboard" import { useToast } from "../ui/toast" +import { CONSOLE_MANAGED_ICON, isConsoleManagedProvider } from "@tui/util/provider-origin" const PROVIDER_PRIORITY: Record = { opencode: 0, @@ -28,87 +29,111 @@ export function createDialogProviderOptions() { const dialog = useDialog() const sdk = useSDK() const toast = useToast() + const { theme } = useTheme() const options = createMemo(() => { return pipe( sync.data.provider_next.all, sortBy((x) => PROVIDER_PRIORITY[x.id] ?? 99), - map((provider) => ({ - title: provider.name, - value: provider.id, - description: { - opencode: "(Recommended)", - anthropic: "(API key)", - openai: "(ChatGPT Plus/Pro or API key)", - "opencode-go": "Low cost subscription for everyone", - }[provider.id], - category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other", - async onSelect() { - const methods = sync.data.provider_auth[provider.id] ?? [ - { - type: "api", - label: "API key", - }, - ] - let index: number | null = 0 - if (methods.length > 1) { - index = await new Promise((resolve) => { - dialog.replace( - () => ( - ({ - title: x.label, - value: index, - }))} - onSelect={(option) => resolve(option.value)} - /> - ), - () => resolve(null), - ) - }) - } - if (index == null) return - const method = methods[index] - if (method.type === "oauth") { - let inputs: Record | undefined - if (method.prompts?.length) { - const value = await PromptsMethod({ - dialog, - prompts: method.prompts, + map((provider) => { + const consoleManaged = isConsoleManagedProvider(sync.data.console_state.consoleManagedProviders, provider.id) + const connected = sync.data.provider_next.connected.includes(provider.id) + + return { + title: provider.name, + value: provider.id, + description: { + opencode: "(Recommended)", + anthropic: "(API key)", + openai: "(ChatGPT Plus/Pro or API key)", + "opencode-go": "Low cost subscription for everyone", + }[provider.id], + footer: consoleManaged ? sync.data.console_state.activeOrgName : undefined, + category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other", + gutter: consoleManaged ? ( + {CONSOLE_MANAGED_ICON} + ) : connected ? ( + + ) : undefined, + async onSelect() { + if (consoleManaged) return + + const methods = sync.data.provider_auth[provider.id] ?? [ + { + type: "api", + label: "API key", + }, + ] + let index: number | null = 0 + if (methods.length > 1) { + index = await new Promise((resolve) => { + dialog.replace( + () => ( + ({ + title: x.label, + value: index, + }))} + onSelect={(option) => resolve(option.value)} + /> + ), + () => resolve(null), + ) }) - if (!value) return - inputs = value } + if (index == null) return + const method = methods[index] + if (method.type === "oauth") { + let inputs: Record | undefined + if (method.prompts?.length) { + const value = await PromptsMethod({ + dialog, + prompts: method.prompts, + }) + if (!value) return + inputs = value + } - const result = await sdk.client.provider.oauth.authorize({ - providerID: provider.id, - method: index, - inputs, - }) - if (result.error) { - toast.show({ - variant: "error", - message: JSON.stringify(result.error), + const result = await sdk.client.provider.oauth.authorize({ + providerID: provider.id, + method: index, + inputs, }) - dialog.clear() - return - } - if (result.data?.method === "code") { - dialog.replace(() => ( - - )) + if (result.error) { + toast.show({ + variant: "error", + message: JSON.stringify(result.error), + }) + dialog.clear() + return + } + if (result.data?.method === "code") { + dialog.replace(() => ( + + )) + } + if (result.data?.method === "auto") { + dialog.replace(() => ( + + )) + } } - if (result.data?.method === "auto") { - dialog.replace(() => ( - - )) + if (method.type === "api") { + return dialog.replace(() => ) } - } - if (method.type === "api") { - return dialog.replace(() => ) - } - }, - })), + }, + } + }), ) }) return options diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 382bd2806ec7..55bf1d563066 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -35,6 +35,7 @@ import { useToast } from "../../ui/toast" import { useKV } from "../../context/kv" import { useTextareaKeybindings } from "../textarea-keybindings" import { DialogSkill } from "../dialog-skill" +import { CONSOLE_MANAGED_ICON, consoleManagedProviderLabel } from "@tui/util/provider-origin" export type PromptProps = { sessionID?: string @@ -94,6 +95,14 @@ export function Prompt(props: PromptProps) { const list = createMemo(() => props.placeholders?.normal ?? []) const shell = createMemo(() => props.placeholders?.shell ?? []) const [auto, setAuto] = createSignal() + const activeOrgName = createMemo(() => sync.data.console_state.activeOrgName) + const currentProviderLabel = createMemo(() => { + const current = local.model.current() + const provider = local.model.parsed().provider + if (!current) return provider + return consoleManagedProviderLabel(sync.data.console_state.consoleManagedProviders, current.providerID, provider) + }) + const hasRightContent = createMemo(() => Boolean(props.right || activeOrgName())) function promptModelWarning() { toast.show({ @@ -1095,7 +1104,7 @@ export function Prompt(props: PromptProps) { {local.model.parsed().model} - {local.model.parsed().provider} + {currentProviderLabel()} · @@ -1105,7 +1114,16 @@ export function Prompt(props: PromptProps) { - {props.right} + + + {props.right} + + command.trigger("console.org.switch")}> + {`${CONSOLE_MANAGED_ICON} ${activeOrgName()}`} + + + + diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 3b296a927aa4..11336d500205 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -29,6 +29,7 @@ import { batch, onMount } from "solid-js" import { Log } from "@/util/log" import type { Path } from "@opencode-ai/sdk" import type { Workspace } from "@opencode-ai/sdk/v2" +import { ConsoleState, emptyConsoleState, type ConsoleState as ConsoleStateType } from "@/config/console-state" export const { use: useSync, provider: SyncProvider } = createSimpleContext({ name: "Sync", @@ -38,6 +39,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ provider: Provider[] provider_default: Record provider_next: ProviderListResponse + console_state: ConsoleStateType provider_auth: Record agent: Agent[] command: Command[] @@ -81,6 +83,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ default: {}, connected: [], }, + console_state: emptyConsoleState, provider_auth: {}, config: {}, status: "loading", @@ -365,6 +368,10 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ // blocking - include session.list when continuing a session const providersPromise = sdk.client.config.providers({}, { throwOnError: true }) const providerListPromise = sdk.client.provider.list({}, { throwOnError: true }) + const consoleStatePromise = sdk.client.experimental.console + .get({}, { throwOnError: true }) + .then((x) => ConsoleState.parse(x.data)) + .catch(() => emptyConsoleState) const agentsPromise = sdk.client.app.agents({}, { throwOnError: true }) const configPromise = sdk.client.config.get({}, { throwOnError: true }) const blockingRequests: Promise[] = [ @@ -379,6 +386,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ .then(() => { const providersResponse = providersPromise.then((x) => x.data!) const providerListResponse = providerListPromise.then((x) => x.data!) + const consoleStateResponse = consoleStatePromise const agentsResponse = agentsPromise.then((x) => x.data ?? []) const configResponse = configPromise.then((x) => x.data!) const sessionListResponse = args.continue ? sessionListPromise : undefined @@ -386,20 +394,23 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ return Promise.all([ providersResponse, providerListResponse, + consoleStateResponse, agentsResponse, configResponse, ...(sessionListResponse ? [sessionListResponse] : []), ]).then((responses) => { const providers = responses[0] const providerList = responses[1] - const agents = responses[2] - const config = responses[3] - const sessions = responses[4] + const consoleState = responses[2] + const agents = responses[3] + const config = responses[4] + const sessions = responses[5] batch(() => { setStore("provider", reconcile(providers.providers)) setStore("provider_default", reconcile(providers.default)) setStore("provider_next", reconcile(providerList)) + setStore("console_state", reconcile(consoleState)) setStore("agent", reconcile(agents)) setStore("config", reconcile(config)) if (sessions !== undefined) setStore("session", reconcile(sessions)) @@ -411,6 +422,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ // non-blocking Promise.all([ ...(args.continue ? [] : [sessionListPromise.then((sessions) => setStore("session", reconcile(sessions)))]), + consoleStatePromise.then((consoleState) => setStore("console_state", reconcile(consoleState))), sdk.client.command.list().then((x) => setStore("command", reconcile(x.data ?? []))), sdk.client.lsp.status().then((x) => setStore("lsp", reconcile(x.data!))), sdk.client.mcp.status().then((x) => setStore("mcp", reconcile(x.data!))), diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx index 30cf3b95437e..46821cccec79 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx @@ -38,6 +38,7 @@ export interface DialogSelectOption { description?: string footer?: JSX.Element | string category?: string + categoryView?: JSX.Element disabled?: boolean bg?: RGBA gutter?: JSX.Element @@ -291,9 +292,16 @@ export function DialogSelect(props: DialogSelectProps) { <> 0 ? 1 : 0} paddingLeft={3}> - - {category} - + + {category} + + } + > + {options[0]?.categoryView} + diff --git a/packages/opencode/src/cli/cmd/tui/util/provider-origin.ts b/packages/opencode/src/cli/cmd/tui/util/provider-origin.ts new file mode 100644 index 000000000000..7ec345ff5212 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/util/provider-origin.ts @@ -0,0 +1,20 @@ +export const CONSOLE_MANAGED_ICON = "⌂" + +const contains = (consoleManagedProviders: string[] | ReadonlySet, providerID: string) => + Array.isArray(consoleManagedProviders) + ? consoleManagedProviders.includes(providerID) + : consoleManagedProviders.has(providerID) + +export const isConsoleManagedProvider = (consoleManagedProviders: string[] | ReadonlySet, providerID: string) => + contains(consoleManagedProviders, providerID) + +export const consoleManagedProviderSuffix = ( + consoleManagedProviders: string[] | ReadonlySet, + providerID: string, +) => (contains(consoleManagedProviders, providerID) ? ` ${CONSOLE_MANAGED_ICON}` : "") + +export const consoleManagedProviderLabel = ( + consoleManagedProviders: string[] | ReadonlySet, + providerID: string, + providerName: string, +) => `${providerName}${consoleManagedProviderSuffix(consoleManagedProviders, providerID)}` diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 850bcc28bcd9..83e677bcb791 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -33,6 +33,7 @@ import { Account } from "@/account" import { isRecord } from "@/util/record" import { ConfigPaths } from "./paths" import { Filesystem } from "@/util/filesystem" +import type { ConsoleState } from "./console-state" import { AppFileSystem } from "@/filesystem" import { InstanceState } from "@/effect/instance-state" import { makeRuntime } from "@/effect/run-service" @@ -1050,11 +1051,13 @@ export namespace Config { config: Info directories: string[] deps: Promise[] + consoleState: ConsoleState } export interface Interface { readonly get: () => Effect.Effect readonly getGlobal: () => Effect.Effect + readonly getConsoleState: () => Effect.Effect readonly update: (config: Info) => Effect.Effect readonly updateGlobal: (config: Info) => Effect.Effect readonly invalidate: (wait?: boolean) => Effect.Effect @@ -1260,6 +1263,8 @@ export namespace Config { const auth = yield* authSvc.all().pipe(Effect.orDie) let result: Info = {} + const consoleManagedProviders = new Set() + let activeOrgName: string | undefined const scope = (source: string): PluginScope => { if (source.startsWith("http://") || source.startsWith("https://")) return "global" @@ -1371,26 +1376,31 @@ export namespace Config { log.debug("loaded custom config from OPENCODE_CONFIG_CONTENT") } - const active = Option.getOrUndefined(yield* accountSvc.active().pipe(Effect.orDie)) - if (active?.active_org_id) { + const activeOrg = Option.getOrUndefined( + yield* accountSvc.activeOrg().pipe(Effect.catch(() => Effect.succeed(Option.none()))), + ) + if (activeOrg) { yield* Effect.gen(function* () { const [configOpt, tokenOpt] = yield* Effect.all( - [accountSvc.config(active.id, active.active_org_id!), accountSvc.token(active.id)], + [accountSvc.config(activeOrg.account.id, activeOrg.org.id), accountSvc.token(activeOrg.account.id)], { concurrency: 2 }, ) - const token = Option.getOrUndefined(tokenOpt) - if (token) { - process.env["OPENCODE_CONSOLE_TOKEN"] = token - Env.set("OPENCODE_CONSOLE_TOKEN", token) + if (Option.isSome(tokenOpt)) { + process.env["OPENCODE_CONSOLE_TOKEN"] = tokenOpt.value + Env.set("OPENCODE_CONSOLE_TOKEN", tokenOpt.value) } - const config = Option.getOrUndefined(configOpt) - if (config) { - const source = `${active.url}/api/config` - const next = yield* loadConfig(JSON.stringify(config), { + activeOrgName = activeOrg.org.name + + if (Option.isSome(configOpt)) { + const source = `${activeOrg.account.url}/api/config` + const next = yield* loadConfig(JSON.stringify(configOpt.value), { dir: path.dirname(source), source, }) + for (const providerID of Object.keys(next.provider ?? {})) { + consoleManagedProviders.add(providerID) + } merge(source, next, "global") } }).pipe( @@ -1456,6 +1466,10 @@ export namespace Config { config: result, directories, deps, + consoleState: { + consoleManagedProviders: Array.from(consoleManagedProviders), + activeOrgName, + }, } }) @@ -1473,6 +1487,10 @@ export namespace Config { return yield* InstanceState.use(state, (s) => s.directories) }) + const getConsoleState = Effect.fn("Config.getConsoleState")(function* () { + return yield* InstanceState.use(state, (s) => s.consoleState) + }) + const waitForDependencies = Effect.fn("Config.waitForDependencies")(function* () { yield* InstanceState.useEffect(state, (s) => Effect.promise(() => Promise.all(s.deps).then(() => undefined))) }) @@ -1528,6 +1546,7 @@ export namespace Config { return Service.of({ get, getGlobal, + getConsoleState, update, updateGlobal, invalidate, @@ -1553,6 +1572,10 @@ export namespace Config { return runPromise((svc) => svc.getGlobal()) } + export async function getConsoleState() { + return runPromise((svc) => svc.getConsoleState()) + } + export async function update(config: Info) { return runPromise((svc) => svc.update(config)) } diff --git a/packages/opencode/src/config/console-state.ts b/packages/opencode/src/config/console-state.ts new file mode 100644 index 000000000000..a5d1f6d2699f --- /dev/null +++ b/packages/opencode/src/config/console-state.ts @@ -0,0 +1,13 @@ +import z from "zod" + +export const ConsoleState = z.object({ + consoleManagedProviders: z.array(z.string()), + activeOrgName: z.string().optional(), +}) + +export type ConsoleState = z.infer + +export const emptyConsoleState: ConsoleState = { + consoleManagedProviders: [], + activeOrgName: undefined, +} diff --git a/packages/opencode/src/server/routes/experimental.ts b/packages/opencode/src/server/routes/experimental.ts index a41b21a1fe9b..a4b1f4d084d3 100644 --- a/packages/opencode/src/server/routes/experimental.ts +++ b/packages/opencode/src/server/routes/experimental.ts @@ -8,13 +8,112 @@ import { Instance } from "../../project/instance" import { Project } from "../../project/project" import { MCP } from "../../mcp" import { Session } from "../../session" +import { Config } from "../../config/config" +import { ConsoleState } from "../../config/console-state" +import { Account, AccountID, OrgID } from "../../account" import { zodToJsonSchema } from "zod-to-json-schema" import { errors } from "../error" import { lazy } from "../../util/lazy" import { WorkspaceRoutes } from "./workspace" +const ConsoleOrgOption = z.object({ + accountID: z.string(), + accountEmail: z.string(), + accountUrl: z.string(), + orgID: z.string(), + orgName: z.string(), + active: z.boolean(), +}) + +const ConsoleOrgList = z.object({ + orgs: z.array(ConsoleOrgOption), +}) + +const ConsoleSwitchBody = z.object({ + accountID: z.string(), + orgID: z.string(), +}) + export const ExperimentalRoutes = lazy(() => new Hono() + .get( + "/console", + describeRoute({ + summary: "Get active Console provider metadata", + description: "Get the active Console org name and the set of provider IDs managed by that Console org.", + operationId: "experimental.console.get", + responses: { + 200: { + description: "Active Console provider metadata", + content: { + "application/json": { + schema: resolver(ConsoleState), + }, + }, + }, + }, + }), + async (c) => { + return c.json(await Config.getConsoleState()) + }, + ) + .get( + "/console/orgs", + describeRoute({ + summary: "List switchable Console orgs", + description: "Get the available Console orgs across logged-in accounts, including the current active org.", + operationId: "experimental.console.listOrgs", + responses: { + 200: { + description: "Switchable Console orgs", + content: { + "application/json": { + schema: resolver(ConsoleOrgList), + }, + }, + }, + }, + }), + async (c) => { + const [groups, active] = await Promise.all([Account.orgsByAccount(), Account.active()]) + + const orgs = groups.flatMap((group) => + group.orgs.map((org) => ({ + accountID: group.account.id, + accountEmail: group.account.email, + accountUrl: group.account.url, + orgID: org.id, + orgName: org.name, + active: !!active && active.id === group.account.id && active.active_org_id === org.id, + })), + ) + return c.json({ orgs }) + }, + ) + .post( + "/console/switch", + describeRoute({ + summary: "Switch active Console org", + description: "Persist a new active Console account/org selection for the current local OpenCode state.", + operationId: "experimental.console.switchOrg", + responses: { + 200: { + description: "Switch success", + content: { + "application/json": { + schema: resolver(z.boolean()), + }, + }, + }, + }, + }), + validator("json", ConsoleSwitchBody), + async (c) => { + const body = c.req.valid("json") + await Account.switchOrg(AccountID.make(body.accountID), OrgID.make(body.orgID)) + return c.json(true) + }, + ) .get( "/tool/ids", describeRoute({ diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 9c631360b620..0ac61aee7172 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -25,6 +25,7 @@ import { Npm } from "../../src/npm" const emptyAccount = Layer.mock(Account.Service)({ active: () => Effect.succeed(Option.none()), + activeOrg: () => Effect.succeed(Option.none()), }) const emptyAuth = Layer.mock(Auth.Service)({ @@ -282,6 +283,21 @@ test("resolves env templates in account config with account token", async () => active_org_id: OrgID.make("org-1"), }), ), + activeOrg: () => + Effect.succeed( + Option.some({ + account: { + id: AccountID.make("account-1"), + email: "user@example.com", + url: "https://control.example.com", + active_org_id: OrgID.make("org-1"), + }, + org: { + id: OrgID.make("org-1"), + name: "Example Org", + }, + }), + ), config: () => Effect.succeed( Option.some({ diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 3a780e234db7..b2e37db59b7e 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -24,6 +24,9 @@ import type { EventTuiPromptAppend, EventTuiSessionSelect, EventTuiToastShow, + ExperimentalConsoleGetResponses, + ExperimentalConsoleListOrgsResponses, + ExperimentalConsoleSwitchOrgResponses, ExperimentalResourceListResponses, ExperimentalSessionListResponses, ExperimentalWorkspaceCreateErrors, @@ -981,13 +984,13 @@ export class Config2 extends HeyApiClient { } } -export class Tool extends HeyApiClient { +export class Console extends HeyApiClient { /** - * List tool IDs + * Get active Console provider metadata * - * Get a list of all available tool IDs, including both built-in tools and dynamically registered tools. + * Get the active Console org name and the set of provider IDs managed by that Console org. */ - public ids( + public get( parameters?: { directory?: string workspace?: string @@ -1005,24 +1008,22 @@ export class Tool extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ - url: "/experimental/tool/ids", + return (options?.client ?? this.client).get({ + url: "/experimental/console", ...options, ...params, }) } /** - * List tools + * List switchable Console orgs * - * Get a list of available tools with their JSON schema parameters for a specific provider and model combination. + * Get the available Console orgs across logged-in accounts, including the current active org. */ - public list( - parameters: { + public listOrgs( + parameters?: { directory?: string workspace?: string - provider: string - model: string }, options?: Options, ) { @@ -1033,18 +1034,55 @@ export class Tool extends HeyApiClient { args: [ { in: "query", key: "directory" }, { in: "query", key: "workspace" }, - { in: "query", key: "provider" }, - { in: "query", key: "model" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/experimental/tool", + return (options?.client ?? this.client).get({ + url: "/experimental/console/orgs", ...options, ...params, }) } + + /** + * Switch active Console org + * + * Persist a new active Console account/org selection for the current local OpenCode state. + */ + public switchOrg( + parameters?: { + directory?: string + workspace?: string + accountID?: string + orgID?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "accountID" }, + { in: "body", key: "orgID" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/experimental/console/switch", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } } export class Workspace extends HeyApiClient { @@ -1239,6 +1277,11 @@ export class Resource extends HeyApiClient { } export class Experimental extends HeyApiClient { + private _console?: Console + get console(): Console { + return (this._console ??= new Console({ client: this.client })) + } + private _workspace?: Workspace get workspace(): Workspace { return (this._workspace ??= new Workspace({ client: this.client })) @@ -1255,6 +1298,72 @@ export class Experimental extends HeyApiClient { } } +export class Tool extends HeyApiClient { + /** + * List tool IDs + * + * Get a list of all available tool IDs, including both built-in tools and dynamically registered tools. + */ + public ids( + parameters?: { + directory?: string + workspace?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/experimental/tool/ids", + ...options, + ...params, + }) + } + + /** + * List tools + * + * Get a list of available tools with their JSON schema parameters for a specific provider and model combination. + */ + public list( + parameters: { + directory?: string + workspace?: string + provider: string + model: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "query", key: "provider" }, + { in: "query", key: "model" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/experimental/tool", + ...options, + ...params, + }) + } +} + export class Worktree extends HeyApiClient { /** * Remove worktree @@ -4017,16 +4126,16 @@ export class OpencodeClient extends HeyApiClient { return (this._config ??= new Config2({ client: this.client })) } - private _tool?: Tool - get tool(): Tool { - return (this._tool ??= new Tool({ client: this.client })) - } - private _experimental?: Experimental get experimental(): Experimental { return (this._experimental ??= new Experimental({ client: this.client })) } + private _tool?: Tool + get tool(): Tool { + return (this._tool ??= new Tool({ client: this.client })) + } + private _worktree?: Worktree get worktree(): Worktree { return (this._worktree ??= new Worktree({ client: this.client })) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index d517abf2c6c1..4c348573f6d1 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2653,6 +2653,80 @@ export type ConfigProvidersResponses = { export type ConfigProvidersResponse = ConfigProvidersResponses[keyof ConfigProvidersResponses] +export type ExperimentalConsoleGetData = { + body?: never + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/experimental/console" +} + +export type ExperimentalConsoleGetResponses = { + /** + * Active Console provider metadata + */ + 200: { + consoleManagedProviders: Array + activeOrgName?: string + } +} + +export type ExperimentalConsoleGetResponse = ExperimentalConsoleGetResponses[keyof ExperimentalConsoleGetResponses] + +export type ExperimentalConsoleListOrgsData = { + body?: never + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/experimental/console/orgs" +} + +export type ExperimentalConsoleListOrgsResponses = { + /** + * Switchable Console orgs + */ + 200: { + orgs: Array<{ + accountID: string + accountEmail: string + accountUrl: string + orgID: string + orgName: string + active: boolean + }> + } +} + +export type ExperimentalConsoleListOrgsResponse = + ExperimentalConsoleListOrgsResponses[keyof ExperimentalConsoleListOrgsResponses] + +export type ExperimentalConsoleSwitchOrgData = { + body?: { + accountID: string + orgID: string + } + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/experimental/console/switch" +} + +export type ExperimentalConsoleSwitchOrgResponses = { + /** + * Switch success + */ + 200: boolean +} + +export type ExperimentalConsoleSwitchOrgResponse = + ExperimentalConsoleSwitchOrgResponses[keyof ExperimentalConsoleSwitchOrgResponses] + export type ToolIdsData = { body?: never path?: never From 9a01df55a6c2d46e84e61fc9448ff1bad094099a Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 4 Apr 2026 19:06:00 -0400 Subject: [PATCH 081/616] refactor(effect): move read tool onto defineEffect (#21016) --- packages/opencode/specs/effect-migration.md | 18 +- packages/opencode/src/filesystem/index.ts | 14 +- .../opencode/src/tool/external-directory.ts | 15 +- packages/opencode/src/tool/read.ts | 412 +++++---- packages/opencode/src/tool/registry.ts | 313 ++++--- packages/opencode/test/tool/read.test.ts | 862 ++++++++---------- 6 files changed, 822 insertions(+), 812 deletions(-) diff --git a/packages/opencode/specs/effect-migration.md b/packages/opencode/specs/effect-migration.md index 9f862d3b9ad5..5882f09fe5fc 100644 --- a/packages/opencode/specs/effect-migration.md +++ b/packages/opencode/specs/effect-migration.md @@ -235,11 +235,27 @@ Once individual tools are effectified, change `Tool.Info` (`tool/tool.ts`) so `i 2. Update `Tool.define()` factory to work with Effects 3. Update `SessionPrompt` to `yield*` tool results instead of `await`ing +### Tool migration details + +Until the tool interface itself returns `Effect`, use this transitional pattern for migrated tools: + +- `Tool.defineEffect(...)` should `yield*` the services the tool depends on and close over them in the returned tool definition. +- Keep the bridge at the Promise boundary only. Prefer a single `Effect.runPromise(...)` in the temporary `async execute(...)` implementation, and move the inner logic into `Effect.fn(...)` helpers instead of scattering `runPromise` islands through the tool body. +- If a tool starts requiring new services, wire them into `ToolRegistry.defaultLayer` so production callers resolve the same dependencies as tests. + +Tool tests should use the existing Effect helpers in `packages/opencode/test/lib/effect.ts`: + +- Use `testEffect(...)` / `it.live(...)` instead of creating fake local wrappers around effectful tools. +- Yield the real tool export, then initialize it: `const info = yield* ReadTool`, `const tool = yield* Effect.promise(() => info.init())`. +- Run tests inside a real instance with `provideTmpdirInstance(...)` or `provideInstance(tmpdirScoped(...))` so instance-scoped services resolve exactly as they do in production. + +This keeps migrated tool tests aligned with the production service graph today, and makes the eventual `Tool.Info` → `Effect` cleanup mostly mechanical later. + Individual tools, ordered by value: - [ ] `apply_patch.ts` — HIGH: multi-step orchestration, error accumulation, Bus events - [ ] `bash.ts` — HIGH: shell orchestration, quoting, timeout handling, output capture -- [ ] `read.ts` — HIGH: streaming I/O, readline, binary detection → FileSystem + Stream +- [x] `read.ts` — HIGH: streaming I/O, readline, binary detection → FileSystem + Stream - [ ] `edit.ts` — HIGH: multi-step diff/format/publish pipeline, FileWatcher lock - [ ] `grep.ts` — MEDIUM: spawns ripgrep → ChildProcessSpawner, timeout handling - [ ] `write.ts` — MEDIUM: permission checks, diagnostics polling, Bus events diff --git a/packages/opencode/src/filesystem/index.ts b/packages/opencode/src/filesystem/index.ts index 45231d43f60c..01fdcd2e5e36 100644 --- a/packages/opencode/src/filesystem/index.ts +++ b/packages/opencode/src/filesystem/index.ts @@ -188,13 +188,23 @@ export namespace AppFileSystem { export function normalizePath(p: string): string { if (process.platform !== "win32") return p + const resolved = pathResolve(windowsPath(p)) try { - return realpathSync.native(p) + return realpathSync.native(resolved) } catch { - return p + return resolved } } + export function normalizePathPattern(p: string): string { + if (process.platform !== "win32") return p + if (p === "*") return p + const match = p.match(/^(.*)[\\/]\*$/) + if (!match) return normalizePath(p) + const dir = /^[A-Za-z]:$/.test(match[1]) ? match[1] + "\\" : match[1] + return join(normalizePath(dir), "*") + } + export function resolve(p: string): string { const resolved = pathResolve(windowsPath(p)) try { diff --git a/packages/opencode/src/tool/external-directory.ts b/packages/opencode/src/tool/external-directory.ts index 66eba438bc6a..f11455cf5975 100644 --- a/packages/opencode/src/tool/external-directory.ts +++ b/packages/opencode/src/tool/external-directory.ts @@ -1,7 +1,8 @@ import path from "path" +import { Effect } from "effect" import type { Tool } from "./tool" import { Instance } from "../project/instance" -import { Filesystem } from "@/util/filesystem" +import { AppFileSystem } from "../filesystem" type Kind = "file" | "directory" @@ -15,14 +16,14 @@ export async function assertExternalDirectory(ctx: Tool.Context, target?: string if (options?.bypass) return - const full = process.platform === "win32" ? Filesystem.normalizePath(target) : target + const full = process.platform === "win32" ? AppFileSystem.normalizePath(target) : target if (Instance.containsPath(full)) return const kind = options?.kind ?? "file" const dir = kind === "directory" ? full : path.dirname(full) const glob = process.platform === "win32" - ? Filesystem.normalizePathPattern(path.join(dir, "*")) + ? AppFileSystem.normalizePathPattern(path.join(dir, "*")) : path.join(dir, "*").replaceAll("\\", "/") await ctx.ask({ @@ -35,3 +36,11 @@ export async function assertExternalDirectory(ctx: Tool.Context, target?: string }, }) } + +export const assertExternalDirectoryEffect = Effect.fn("Tool.assertExternalDirectory")(function* ( + ctx: Tool.Context, + target?: string, + options?: Options, +) { + yield* Effect.promise(() => assertExternalDirectory(ctx, target, options)) +}) diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index 18520c2a6f6a..366993020ba1 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -1,16 +1,17 @@ import z from "zod" +import { Effect, Scope } from "effect" import { createReadStream } from "fs" -import * as fs from "fs/promises" +import { open } from "fs/promises" import * as path from "path" import { createInterface } from "readline" import { Tool } from "./tool" +import { AppFileSystem } from "../filesystem" import { LSP } from "../lsp" import { FileTime } from "../file/time" import DESCRIPTION from "./read.txt" import { Instance } from "../project/instance" -import { assertExternalDirectory } from "./external-directory" +import { assertExternalDirectoryEffect } from "./external-directory" import { Instruction } from "../session/instruction" -import { Filesystem } from "../util/filesystem" const DEFAULT_READ_LIMIT = 2000 const MAX_LINE_LENGTH = 2000 @@ -18,222 +19,257 @@ const MAX_LINE_SUFFIX = `... (line truncated to ${MAX_LINE_LENGTH} chars)` const MAX_BYTES = 50 * 1024 const MAX_BYTES_LABEL = `${MAX_BYTES / 1024} KB` -export const ReadTool = Tool.define("read", { - description: DESCRIPTION, - parameters: z.object({ - filePath: z.string().describe("The absolute path to the file or directory to read"), - offset: z.coerce.number().describe("The line number to start reading from (1-indexed)").optional(), - limit: z.coerce.number().describe("The maximum number of lines to read (defaults to 2000)").optional(), - }), - async execute(params, ctx) { - if (params.offset !== undefined && params.offset < 1) { - throw new Error("offset must be greater than or equal to 1") - } - let filepath = params.filePath - if (!path.isAbsolute(filepath)) { - filepath = path.resolve(Instance.directory, filepath) - } - if (process.platform === "win32") { - filepath = Filesystem.normalizePath(filepath) - } - const title = path.relative(Instance.worktree, filepath) - - const stat = Filesystem.stat(filepath) +const parameters = z.object({ + filePath: z.string().describe("The absolute path to the file or directory to read"), + offset: z.coerce.number().describe("The line number to start reading from (1-indexed)").optional(), + limit: z.coerce.number().describe("The maximum number of lines to read (defaults to 2000)").optional(), +}) - await assertExternalDirectory(ctx, filepath, { - bypass: Boolean(ctx.extra?.["bypassCwdCheck"]), - kind: stat?.isDirectory() ? "directory" : "file", - }) +export const ReadTool = Tool.defineEffect( + "read", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const instruction = yield* Instruction.Service + const lsp = yield* LSP.Service + const time = yield* FileTime.Service + const scope = yield* Scope.Scope - await ctx.ask({ - permission: "read", - patterns: [filepath], - always: ["*"], - metadata: {}, - }) - - if (!stat) { + const miss = Effect.fn("ReadTool.miss")(function* (filepath: string) { const dir = path.dirname(filepath) const base = path.basename(filepath) - - const suggestions = await fs - .readdir(dir) - .then((entries) => - entries + const items = yield* fs.readDirectory(dir).pipe( + Effect.map((items) => + items .filter( - (entry) => - entry.toLowerCase().includes(base.toLowerCase()) || base.toLowerCase().includes(entry.toLowerCase()), + (item) => + item.toLowerCase().includes(base.toLowerCase()) || base.toLowerCase().includes(item.toLowerCase()), ) - .map((entry) => path.join(dir, entry)) + .map((item) => path.join(dir, item)) .slice(0, 3), - ) - .catch(() => []) + ), + Effect.catch(() => Effect.succeed([] as string[])), + ) - if (suggestions.length > 0) { - throw new Error(`File not found: ${filepath}\n\nDid you mean one of these?\n${suggestions.join("\n")}`) + if (items.length > 0) { + return yield* Effect.fail( + new Error(`File not found: ${filepath}\n\nDid you mean one of these?\n${items.join("\n")}`), + ) } - throw new Error(`File not found: ${filepath}`) - } + return yield* Effect.fail(new Error(`File not found: ${filepath}`)) + }) - if (stat.isDirectory()) { - const dirents = await fs.readdir(filepath, { withFileTypes: true }) - const entries = await Promise.all( - dirents.map(async (dirent) => { - if (dirent.isDirectory()) return dirent.name + "/" - if (dirent.isSymbolicLink()) { - const target = await fs.stat(path.join(filepath, dirent.name)).catch(() => undefined) - if (target?.isDirectory()) return dirent.name + "/" - } - return dirent.name + const list = Effect.fn("ReadTool.list")(function* (filepath: string) { + const items = yield* fs.readDirectoryEntries(filepath) + return yield* Effect.forEach( + items, + Effect.fnUntraced(function* (item) { + if (item.type === "directory") return item.name + "/" + if (item.type !== "symlink") return item.name + + const target = yield* fs + .stat(path.join(filepath, item.name)) + .pipe(Effect.catch(() => Effect.succeed(undefined))) + if (target?.type === "Directory") return item.name + "/" + return item.name }), - ) - entries.sort((a, b) => a.localeCompare(b)) - - const limit = params.limit ?? DEFAULT_READ_LIMIT - const offset = params.offset ?? 1 - const start = offset - 1 - const sliced = entries.slice(start, start + limit) - const truncated = start + sliced.length < entries.length - - const output = [ - `${filepath}`, - `directory`, - ``, - sliced.join("\n"), - truncated - ? `\n(Showing ${sliced.length} of ${entries.length} entries. Use 'offset' parameter to read beyond entry ${offset + sliced.length})` - : `\n(${entries.length} entries)`, - ``, - ].join("\n") + { concurrency: "unbounded" }, + ).pipe(Effect.map((items: string[]) => items.sort((a, b) => a.localeCompare(b)))) + }) - return { - title, - output, - metadata: { - preview: sliced.slice(0, 20).join("\n"), - truncated, - loaded: [] as string[], - }, - } - } + const warm = Effect.fn("ReadTool.warm")(function* (filepath: string, sessionID: Tool.Context["sessionID"]) { + yield* lsp.touchFile(filepath, false).pipe(Effect.ignore, Effect.forkIn(scope)) + yield* time.read(sessionID, filepath) + }) - const instructions = await Instruction.resolve(ctx.messages, filepath, ctx.messageID) + const run = Effect.fn("ReadTool.execute")(function* (params: z.infer, ctx: Tool.Context) { + if (params.offset !== undefined && params.offset < 1) { + return yield* Effect.fail(new Error("offset must be greater than or equal to 1")) + } - // Exclude SVG (XML-based) and vnd.fastbidsheet (.fbs extension, commonly FlatBuffers schema files) - const mime = Filesystem.mimeType(filepath) - const isImage = mime.startsWith("image/") && mime !== "image/svg+xml" && mime !== "image/vnd.fastbidsheet" - const isPdf = mime === "application/pdf" - if (isImage || isPdf) { - const msg = `${isImage ? "Image" : "PDF"} read successfully` - return { - title, - output: msg, - metadata: { - preview: msg, - truncated: false, - loaded: instructions.map((i) => i.filepath), - }, - attachments: [ - { - type: "file", - mime, - url: `data:${mime};base64,${Buffer.from(await Filesystem.readBytes(filepath)).toString("base64")}`, - }, - ], + let filepath = params.filePath + if (!path.isAbsolute(filepath)) { + filepath = path.resolve(Instance.directory, filepath) } - } + if (process.platform === "win32") { + filepath = AppFileSystem.normalizePath(filepath) + } + const title = path.relative(Instance.worktree, filepath) - const isBinary = await isBinaryFile(filepath, Number(stat.size)) - if (isBinary) throw new Error(`Cannot read binary file: ${filepath}`) + const stat = yield* fs.stat(filepath).pipe( + Effect.catchIf( + (err) => "reason" in err && err.reason._tag === "NotFound", + () => Effect.succeed(undefined), + ), + ) - const stream = createReadStream(filepath, { encoding: "utf8" }) - const rl = createInterface({ - input: stream, - // Note: we use the crlfDelay option to recognize all instances of CR LF - // ('\r\n') in file as a single line break. - crlfDelay: Infinity, - }) + yield* assertExternalDirectoryEffect(ctx, filepath, { + bypass: Boolean(ctx.extra?.["bypassCwdCheck"]), + kind: stat?.type === "Directory" ? "directory" : "file", + }) + + yield* Effect.promise(() => + ctx.ask({ + permission: "read", + patterns: [filepath], + always: ["*"], + metadata: {}, + }), + ) - const limit = params.limit ?? DEFAULT_READ_LIMIT - const offset = params.offset ?? 1 - const start = offset - 1 - const raw: string[] = [] - let bytes = 0 - let lines = 0 - let truncatedByBytes = false - let hasMoreLines = false - try { - for await (const text of rl) { - lines += 1 - if (lines <= start) continue - - if (raw.length >= limit) { - hasMoreLines = true - continue + if (!stat) return yield* miss(filepath) + + if (stat.type === "Directory") { + const items = yield* list(filepath) + const limit = params.limit ?? DEFAULT_READ_LIMIT + const offset = params.offset ?? 1 + const start = offset - 1 + const sliced = items.slice(start, start + limit) + const truncated = start + sliced.length < items.length + + return { + title, + output: [ + `${filepath}`, + `directory`, + ``, + sliced.join("\n"), + truncated + ? `\n(Showing ${sliced.length} of ${items.length} entries. Use 'offset' parameter to read beyond entry ${offset + sliced.length})` + : `\n(${items.length} entries)`, + ``, + ].join("\n"), + metadata: { + preview: sliced.slice(0, 20).join("\n"), + truncated, + loaded: [] as string[], + }, } + } - const line = text.length > MAX_LINE_LENGTH ? text.substring(0, MAX_LINE_LENGTH) + MAX_LINE_SUFFIX : text - const size = Buffer.byteLength(line, "utf-8") + (raw.length > 0 ? 1 : 0) - if (bytes + size > MAX_BYTES) { - truncatedByBytes = true - hasMoreLines = true - break + const loaded = yield* instruction.resolve(ctx.messages, filepath, ctx.messageID) + + const mime = AppFileSystem.mimeType(filepath) + const isImage = mime.startsWith("image/") && mime !== "image/svg+xml" && mime !== "image/vnd.fastbidsheet" + const isPdf = mime === "application/pdf" + if (isImage || isPdf) { + const msg = `${isImage ? "Image" : "PDF"} read successfully` + return { + title, + output: msg, + metadata: { + preview: msg, + truncated: false, + loaded: loaded.map((item) => item.filepath), + }, + attachments: [ + { + type: "file" as const, + mime, + url: `data:${mime};base64,${Buffer.from(yield* fs.readFile(filepath)).toString("base64")}`, + }, + ], } + } - raw.push(line) - bytes += size + if (yield* Effect.promise(() => isBinaryFile(filepath, Number(stat.size)))) { + return yield* Effect.fail(new Error(`Cannot read binary file: ${filepath}`)) } - } finally { - rl.close() - stream.destroy() - } - if (lines < offset && !(lines === 0 && offset === 1)) { - throw new Error(`Offset ${offset} is out of range for this file (${lines} lines)`) - } + const file = yield* Effect.promise(() => + lines(filepath, { limit: params.limit ?? DEFAULT_READ_LIMIT, offset: params.offset ?? 1 }), + ) + if (file.count < file.offset && !(file.count === 0 && file.offset === 1)) { + return yield* Effect.fail( + new Error(`Offset ${file.offset} is out of range for this file (${file.count} lines)`), + ) + } - const content = raw.map((line, index) => { - return `${index + offset}: ${line}` - }) - const preview = raw.slice(0, 20).join("\n") - - let output = [`${filepath}`, `file`, ""].join("\n") - output += content.join("\n") - - const totalLines = lines - const lastReadLine = offset + raw.length - 1 - const nextOffset = lastReadLine + 1 - const truncated = hasMoreLines || truncatedByBytes - - if (truncatedByBytes) { - output += `\n\n(Output capped at ${MAX_BYTES_LABEL}. Showing lines ${offset}-${lastReadLine}. Use offset=${nextOffset} to continue.)` - } else if (hasMoreLines) { - output += `\n\n(Showing lines ${offset}-${lastReadLine} of ${totalLines}. Use offset=${nextOffset} to continue.)` - } else { - output += `\n\n(End of file - total ${totalLines} lines)` - } - output += "\n" + let output = [`${filepath}`, `file`, ""].join("\n") + output += file.raw.map((line, i) => `${i + file.offset}: ${line}`).join("\n") + + const last = file.offset + file.raw.length - 1 + const next = last + 1 + const truncated = file.more || file.cut + if (file.cut) { + output += `\n\n(Output capped at ${MAX_BYTES_LABEL}. Showing lines ${file.offset}-${last}. Use offset=${next} to continue.)` + } else if (file.more) { + output += `\n\n(Showing lines ${file.offset}-${last} of ${file.count}. Use offset=${next} to continue.)` + } else { + output += `\n\n(End of file - total ${file.count} lines)` + } + output += "\n" - // just warms the lsp client - LSP.touchFile(filepath, false) - await FileTime.read(ctx.sessionID, filepath) + yield* warm(filepath, ctx.sessionID) - if (instructions.length > 0) { - output += `\n\n\n${instructions.map((i) => i.content).join("\n\n")}\n` - } + if (loaded.length > 0) { + output += `\n\n\n${loaded.map((item) => item.content).join("\n\n")}\n` + } + + return { + title, + output, + metadata: { + preview: file.raw.slice(0, 20).join("\n"), + truncated, + loaded: loaded.map((item) => item.filepath), + }, + } + }) return { - title, - output, - metadata: { - preview, - truncated, - loaded: instructions.map((i) => i.filepath), + description: DESCRIPTION, + parameters, + async execute(params: z.infer, ctx) { + return Effect.runPromise(run(params, ctx).pipe(Effect.orDie)) }, } - }, -}) + }), +) + +async function lines(filepath: string, opts: { limit: number; offset: number }) { + const stream = createReadStream(filepath, { encoding: "utf8" }) + const rl = createInterface({ + input: stream, + // Note: we use the crlfDelay option to recognize all instances of CR LF + // ('\r\n') in file as a single line break. + crlfDelay: Infinity, + }) + + const start = opts.offset - 1 + const raw: string[] = [] + let bytes = 0 + let count = 0 + let cut = false + let more = false + try { + for await (const text of rl) { + count += 1 + if (count <= start) continue + + if (raw.length >= opts.limit) { + more = true + continue + } + + const line = text.length > MAX_LINE_LENGTH ? text.substring(0, MAX_LINE_LENGTH) + MAX_LINE_SUFFIX : text + const size = Buffer.byteLength(line, "utf-8") + (raw.length > 0 ? 1 : 0) + if (bytes + size > MAX_BYTES) { + cut = true + more = true + break + } + + raw.push(line) + bytes += size + } + } finally { + rl.close() + stream.destroy() + } + + return { raw, count, cut, more, offset: opts.offset } +} async function isBinaryFile(filepath: string, fileSize: number): Promise { const ext = path.extname(filepath).toLowerCase() @@ -274,7 +310,7 @@ async function isBinaryFile(filepath: string, fileSize: number): Promise()("@opencode/ToolRegistry") {} - export const layer: Layer.Layer = - Layer.effect( - Service, - Effect.gen(function* () { - const config = yield* Config.Service - const plugin = yield* Plugin.Service - - const build = (tool: T | Effect.Effect) => - Effect.isEffect(tool) ? tool : Effect.succeed(tool) - - const state = yield* InstanceState.make( - Effect.fn("ToolRegistry.state")(function* (ctx) { - const custom: Tool.Info[] = [] - - function fromPlugin(id: string, def: ToolDefinition): Tool.Info { - return { - id, - init: async (initCtx) => ({ - parameters: z.object(def.args), - description: def.description, - execute: async (args, toolCtx) => { - const pluginCtx = { - ...toolCtx, - directory: ctx.directory, - worktree: ctx.worktree, - } as unknown as PluginToolContext - const result = await def.execute(args as any, pluginCtx) - const out = await Truncate.output(result, {}, initCtx?.agent) - return { - title: "", - output: out.truncated ? out.content : result, - metadata: { truncated: out.truncated, outputPath: out.truncated ? out.outputPath : undefined }, - } - }, - }), - } + export const layer: Layer.Layer< + Service, + never, + | Config.Service + | Plugin.Service + | Question.Service + | Todo.Service + | LSP.Service + | FileTime.Service + | Instruction.Service + | AppFileSystem.Service + > = Layer.effect( + Service, + Effect.gen(function* () { + const config = yield* Config.Service + const plugin = yield* Plugin.Service + + const build = (tool: T | Effect.Effect) => + Effect.isEffect(tool) ? tool : Effect.succeed(tool) + + const state = yield* InstanceState.make( + Effect.fn("ToolRegistry.state")(function* (ctx) { + const custom: Tool.Info[] = [] + + function fromPlugin(id: string, def: ToolDefinition): Tool.Info { + return { + id, + init: async (initCtx) => ({ + parameters: z.object(def.args), + description: def.description, + execute: async (args, toolCtx) => { + const pluginCtx = { + ...toolCtx, + directory: ctx.directory, + worktree: ctx.worktree, + } as unknown as PluginToolContext + const result = await def.execute(args as any, pluginCtx) + const out = await Truncate.output(result, {}, initCtx?.agent) + return { + title: "", + output: out.truncated ? out.content : result, + metadata: { truncated: out.truncated, outputPath: out.truncated ? out.outputPath : undefined }, + } + }, + }), } + } - const dirs = yield* config.directories() - const matches = dirs.flatMap((dir) => - Glob.scanSync("{tool,tools}/*.{js,ts}", { cwd: dir, absolute: true, dot: true, symlink: true }), + const dirs = yield* config.directories() + const matches = dirs.flatMap((dir) => + Glob.scanSync("{tool,tools}/*.{js,ts}", { cwd: dir, absolute: true, dot: true, symlink: true }), + ) + if (matches.length) yield* config.waitForDependencies() + for (const match of matches) { + const namespace = path.basename(match, path.extname(match)) + const mod = yield* Effect.promise( + () => import(process.platform === "win32" ? match : pathToFileURL(match).href), ) - if (matches.length) yield* config.waitForDependencies() - for (const match of matches) { - const namespace = path.basename(match, path.extname(match)) - const mod = yield* Effect.promise( - () => import(process.platform === "win32" ? match : pathToFileURL(match).href), - ) - for (const [id, def] of Object.entries(mod)) { - custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def)) - } + for (const [id, def] of Object.entries(mod)) { + custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def)) } + } - const plugins = yield* plugin.list() - for (const p of plugins) { - for (const [id, def] of Object.entries(p.tool ?? {})) { - custom.push(fromPlugin(id, def)) - } + const plugins = yield* plugin.list() + for (const p of plugins) { + for (const [id, def] of Object.entries(p.tool ?? {})) { + custom.push(fromPlugin(id, def)) } + } - return { custom } - }), - ) + return { custom } + }), + ) - const invalid = yield* build(InvalidTool) - const ask = yield* build(QuestionTool) - const bash = yield* build(BashTool) - const read = yield* build(ReadTool) - const glob = yield* build(GlobTool) - const grep = yield* build(GrepTool) - const edit = yield* build(EditTool) - const write = yield* build(WriteTool) - const task = yield* build(TaskTool) - const fetch = yield* build(WebFetchTool) - const todo = yield* build(TodoWriteTool) - const search = yield* build(WebSearchTool) - const code = yield* build(CodeSearchTool) - const skill = yield* build(SkillTool) - const patch = yield* build(ApplyPatchTool) - const lsp = yield* build(LspTool) - const batch = yield* build(BatchTool) - const plan = yield* build(PlanExitTool) - - const all = Effect.fn("ToolRegistry.all")(function* (custom: Tool.Info[]) { - const cfg = yield* config.get() - const question = - ["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || Flag.OPENCODE_ENABLE_QUESTION_TOOL - - return [ - invalid, - ...(question ? [ask] : []), - bash, - read, - glob, - grep, - edit, - write, - task, - fetch, - todo, - search, - code, - skill, - patch, - ...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [lsp] : []), - ...(cfg.experimental?.batch_tool === true ? [batch] : []), - ...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [plan] : []), - ...custom, - ] - }) + const invalid = yield* build(InvalidTool) + const ask = yield* build(QuestionTool) + const bash = yield* build(BashTool) + const read = yield* build(ReadTool) + const glob = yield* build(GlobTool) + const grep = yield* build(GrepTool) + const edit = yield* build(EditTool) + const write = yield* build(WriteTool) + const task = yield* build(TaskTool) + const fetch = yield* build(WebFetchTool) + const todo = yield* build(TodoWriteTool) + const search = yield* build(WebSearchTool) + const code = yield* build(CodeSearchTool) + const skill = yield* build(SkillTool) + const patch = yield* build(ApplyPatchTool) + const lsp = yield* build(LspTool) + const batch = yield* build(BatchTool) + const plan = yield* build(PlanExitTool) - const ids = Effect.fn("ToolRegistry.ids")(function* () { - const s = yield* InstanceState.get(state) - const tools = yield* all(s.custom) - return tools.map((t) => t.id) - }) + const all = Effect.fn("ToolRegistry.all")(function* (custom: Tool.Info[]) { + const cfg = yield* config.get() + const question = ["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || Flag.OPENCODE_ENABLE_QUESTION_TOOL - const tools = Effect.fn("ToolRegistry.tools")(function* ( - model: { providerID: ProviderID; modelID: ModelID }, - agent?: Agent.Info, - ) { - const s = yield* InstanceState.get(state) - const allTools = yield* all(s.custom) - const filtered = allTools.filter((tool) => { - if (tool.id === "codesearch" || tool.id === "websearch") { - return model.providerID === ProviderID.opencode || Flag.OPENCODE_ENABLE_EXA - } + return [ + invalid, + ...(question ? [ask] : []), + bash, + read, + glob, + grep, + edit, + write, + task, + fetch, + todo, + search, + code, + skill, + patch, + ...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [lsp] : []), + ...(cfg.experimental?.batch_tool === true ? [batch] : []), + ...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [plan] : []), + ...custom, + ] + }) - const usePatch = - !!Env.get("OPENCODE_E2E_LLM_URL") || - (model.modelID.includes("gpt-") && !model.modelID.includes("oss") && !model.modelID.includes("gpt-4")) - if (tool.id === "apply_patch") return usePatch - if (tool.id === "edit" || tool.id === "write") return !usePatch - - return true - }) - return yield* Effect.forEach( - filtered, - Effect.fnUntraced(function* (tool: Tool.Info) { - using _ = log.time(tool.id) - const next = yield* Effect.promise(() => tool.init({ agent })) - const output = { - description: next.description, - parameters: next.parameters, - } - yield* plugin.trigger("tool.definition", { toolID: tool.id }, output) - return { - id: tool.id, - description: output.description, - parameters: output.parameters, - execute: next.execute, - formatValidationError: next.formatValidationError, - } - }), - { concurrency: "unbounded" }, - ) + const ids = Effect.fn("ToolRegistry.ids")(function* () { + const s = yield* InstanceState.get(state) + const tools = yield* all(s.custom) + return tools.map((t) => t.id) + }) + + const tools = Effect.fn("ToolRegistry.tools")(function* ( + model: { providerID: ProviderID; modelID: ModelID }, + agent?: Agent.Info, + ) { + const s = yield* InstanceState.get(state) + const allTools = yield* all(s.custom) + const filtered = allTools.filter((tool) => { + if (tool.id === "codesearch" || tool.id === "websearch") { + return model.providerID === ProviderID.opencode || Flag.OPENCODE_ENABLE_EXA + } + + const usePatch = + !!Env.get("OPENCODE_E2E_LLM_URL") || + (model.modelID.includes("gpt-") && !model.modelID.includes("oss") && !model.modelID.includes("gpt-4")) + if (tool.id === "apply_patch") return usePatch + if (tool.id === "edit" || tool.id === "write") return !usePatch + + return true }) + return yield* Effect.forEach( + filtered, + Effect.fnUntraced(function* (tool: Tool.Info) { + using _ = log.time(tool.id) + const next = yield* Effect.promise(() => tool.init({ agent })) + const output = { + description: next.description, + parameters: next.parameters, + } + yield* plugin.trigger("tool.definition", { toolID: tool.id }, output) + return { + id: tool.id, + description: output.description, + parameters: output.parameters, + execute: next.execute, + formatValidationError: next.formatValidationError, + } + }), + { concurrency: "unbounded" }, + ) + }) - return Service.of({ ids, named: { task, read }, tools }) - }), - ) + return Service.of({ ids, named: { task, read }, tools }) + }), + ) export const defaultLayer = Layer.unwrap( Effect.sync(() => @@ -226,6 +239,10 @@ export namespace ToolRegistry { Layer.provide(Plugin.defaultLayer), Layer.provide(Question.defaultLayer), Layer.provide(Todo.defaultLayer), + Layer.provide(LSP.defaultLayer), + Layer.provide(FileTime.defaultLayer), + Layer.provide(Instruction.defaultLayer), + Layer.provide(AppFileSystem.defaultLayer), ), ), ) diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index d58565f433e2..12345266b318 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -1,12 +1,20 @@ -import { afterEach, describe, expect, test } from "bun:test" +import { afterEach, describe, expect } from "bun:test" +import { Cause, Effect, Exit, Layer } from "effect" import path from "path" -import { ReadTool } from "../../src/tool/read" -import { Instance } from "../../src/project/instance" -import { Filesystem } from "../../src/util/filesystem" -import { tmpdir } from "../fixture/fixture" -import { Permission } from "../../src/permission" import { Agent } from "../../src/agent/agent" +import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { AppFileSystem } from "../../src/filesystem" +import { FileTime } from "../../src/file/time" +import { LSP } from "../../src/lsp" +import { Permission } from "../../src/permission" +import { Instance } from "../../src/project/instance" import { SessionID, MessageID } from "../../src/session/schema" +import { Instruction } from "../../src/session/instruction" +import { ReadTool } from "../../src/tool/read" +import { Tool } from "../../src/tool/tool" +import { Filesystem } from "../../src/util/filesystem" +import { provideInstance, tmpdirScoped } from "../fixture/fixture" +import { testEffect } from "../lib/effect" const FIXTURES_DIR = path.join(import.meta.dir, "fixtures") @@ -25,173 +33,171 @@ const ctx = { ask: async () => {}, } +const it = testEffect( + Layer.mergeAll( + Agent.defaultLayer, + AppFileSystem.defaultLayer, + CrossSpawnSpawner.defaultLayer, + FileTime.defaultLayer, + Instruction.defaultLayer, + LSP.defaultLayer, + ), +) + +const init = Effect.fn("ReadToolTest.init")(function* () { + const info = yield* ReadTool + return yield* Effect.promise(() => info.init()) +}) + +const run = Effect.fn("ReadToolTest.run")(function* ( + args: Tool.InferParameters, + next: Tool.Context = ctx, +) { + const tool = yield* init() + return yield* Effect.promise(() => tool.execute(args, next)) +}) + +const exec = Effect.fn("ReadToolTest.exec")(function* ( + dir: string, + args: Tool.InferParameters, + next: Tool.Context = ctx, +) { + return yield* provideInstance(dir)(run(args, next)) +}) + +const fail = Effect.fn("ReadToolTest.fail")(function* ( + dir: string, + args: Tool.InferParameters, + next: Tool.Context = ctx, +) { + const exit = yield* exec(dir, args, next).pipe(Effect.exit) + if (Exit.isFailure(exit)) { + const err = Cause.squash(exit.cause) + return err instanceof Error ? err : new Error(String(err)) + } + throw new Error("expected read to fail") +}) + const full = (p: string) => (process.platform === "win32" ? Filesystem.normalizePath(p) : p) const glob = (p: string) => process.platform === "win32" ? Filesystem.normalizePathPattern(p) : p.replaceAll("\\", "/") +const put = Effect.fn("ReadToolTest.put")(function* (p: string, content: string | Buffer | Uint8Array) { + const fs = yield* AppFileSystem.Service + yield* fs.writeWithDirs(p, content) +}) +const load = Effect.fn("ReadToolTest.load")(function* (p: string) { + const fs = yield* AppFileSystem.Service + return yield* fs.readFileString(p) +}) +const asks = () => { + const items: Array> = [] + return { + items, + next: { + ...ctx, + ask: async (req: Omit) => { + items.push(req) + }, + }, + } +} describe("tool.read external_directory permission", () => { - test("allows reading absolute path inside project directory", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "test.txt"), "hello world") - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const result = await read.execute({ filePath: path.join(tmp.path, "test.txt") }, ctx) - expect(result.output).toContain("hello world") - }, - }) - }) + it.live("allows reading absolute path inside project directory", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + yield* put(path.join(dir, "test.txt"), "hello world") - test("allows reading file in subdirectory inside project directory", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "subdir", "test.txt"), "nested content") - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const result = await read.execute({ filePath: path.join(tmp.path, "subdir", "test.txt") }, ctx) - expect(result.output).toContain("nested content") - }, - }) - }) + const result = yield* exec(dir, { filePath: path.join(dir, "test.txt") }) + expect(result.output).toContain("hello world") + }), + ) - test("asks for external_directory permission when reading absolute path outside project", async () => { - await using outerTmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "secret.txt"), "secret data") - }, - }) - await using tmp = await tmpdir({ git: true }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const requests: Array> = [] - const testCtx = { - ...ctx, - ask: async (req: Omit) => { - requests.push(req) - }, - } - await read.execute({ filePath: path.join(outerTmp.path, "secret.txt") }, testCtx) - const extDirReq = requests.find((r) => r.permission === "external_directory") - expect(extDirReq).toBeDefined() - expect(extDirReq!.patterns).toContain(glob(path.join(outerTmp.path, "*"))) - }, - }) - }) + it.live("allows reading file in subdirectory inside project directory", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + yield* put(path.join(dir, "subdir", "test.txt"), "nested content") + + const result = yield* exec(dir, { filePath: path.join(dir, "subdir", "test.txt") }) + expect(result.output).toContain("nested content") + }), + ) + + it.live("asks for external_directory permission when reading absolute path outside project", () => + Effect.gen(function* () { + const outer = yield* tmpdirScoped() + const dir = yield* tmpdirScoped({ git: true }) + yield* put(path.join(outer, "secret.txt"), "secret data") + + const { items, next } = asks() + + yield* exec(dir, { filePath: path.join(outer, "secret.txt") }, next) + const ext = items.find((item) => item.permission === "external_directory") + expect(ext).toBeDefined() + expect(ext!.patterns).toContain(glob(path.join(outer, "*"))) + }), + ) if (process.platform === "win32") { - test("normalizes read permission paths on Windows", async () => { - await using tmp = await tmpdir({ - git: true, - init: async (dir) => { - await Bun.write(path.join(dir, "test.txt"), "hello world") - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const requests: Array> = [] - const testCtx = { - ...ctx, - ask: async (req: Omit) => { - requests.push(req) - }, - } - const target = path.join(tmp.path, "test.txt") - const alt = target - .replace(/^[A-Za-z]:/, "") - .replaceAll("\\", "/") - .toLowerCase() - await read.execute({ filePath: alt }, testCtx) - const readReq = requests.find((r) => r.permission === "read") - expect(readReq).toBeDefined() - expect(readReq!.patterns).toEqual([full(target)]) - }, - }) - }) + it.live("normalizes read permission paths on Windows", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped({ git: true }) + yield* put(path.join(dir, "test.txt"), "hello world") + + const { items, next } = asks() + const target = path.join(dir, "test.txt") + const alt = target + .replace(/^[A-Za-z]:/, "") + .replaceAll("\\", "/") + .toLowerCase() + + yield* exec(dir, { filePath: alt }, next) + const read = items.find((item) => item.permission === "read") + expect(read).toBeDefined() + expect(read!.patterns).toEqual([full(target)]) + }), + ) } - test("asks for directory-scoped external_directory permission when reading external directory", async () => { - await using outerTmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "external", "a.txt"), "a") - }, - }) - await using tmp = await tmpdir({ git: true }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const requests: Array> = [] - const testCtx = { - ...ctx, - ask: async (req: Omit) => { - requests.push(req) - }, - } - await read.execute({ filePath: path.join(outerTmp.path, "external") }, testCtx) - const extDirReq = requests.find((r) => r.permission === "external_directory") - expect(extDirReq).toBeDefined() - expect(extDirReq!.patterns).toContain(glob(path.join(outerTmp.path, "external", "*"))) - }, - }) - }) - - test("asks for external_directory permission when reading relative path outside project", async () => { - await using tmp = await tmpdir({ git: true }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const requests: Array> = [] - const testCtx = { - ...ctx, - ask: async (req: Omit) => { - requests.push(req) - }, - } - // This will fail because file doesn't exist, but we can check if permission was asked - await read.execute({ filePath: "../outside.txt" }, testCtx).catch(() => {}) - const extDirReq = requests.find((r) => r.permission === "external_directory") - expect(extDirReq).toBeDefined() - }, - }) - }) + it.live("asks for directory-scoped external_directory permission when reading external directory", () => + Effect.gen(function* () { + const outer = yield* tmpdirScoped() + const dir = yield* tmpdirScoped({ git: true }) + yield* put(path.join(outer, "external", "a.txt"), "a") - test("does not ask for external_directory permission when reading inside project", async () => { - await using tmp = await tmpdir({ - git: true, - init: async (dir) => { - await Bun.write(path.join(dir, "internal.txt"), "internal content") - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const requests: Array> = [] - const testCtx = { - ...ctx, - ask: async (req: Omit) => { - requests.push(req) - }, - } - await read.execute({ filePath: path.join(tmp.path, "internal.txt") }, testCtx) - const extDirReq = requests.find((r) => r.permission === "external_directory") - expect(extDirReq).toBeUndefined() - }, - }) - }) + const { items, next } = asks() + + yield* exec(dir, { filePath: path.join(outer, "external") }, next) + const ext = items.find((item) => item.permission === "external_directory") + expect(ext).toBeDefined() + expect(ext!.patterns).toContain(glob(path.join(outer, "external", "*"))) + }), + ) + + it.live("asks for external_directory permission when reading relative path outside project", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped({ git: true }) + + const { items, next } = asks() + + yield* fail(dir, { filePath: "../outside.txt" }, next) + const ext = items.find((item) => item.permission === "external_directory") + expect(ext).toBeDefined() + }), + ) + + it.live("does not ask for external_directory permission when reading inside project", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped({ git: true }) + yield* put(path.join(dir, "internal.txt"), "internal content") + + const { items, next } = asks() + + yield* exec(dir, { filePath: path.join(dir, "internal.txt") }, next) + const ext = items.find((item) => item.permission === "external_directory") + expect(ext).toBeUndefined() + }), + ) }) describe("tool.read env file permissions", () => { @@ -205,261 +211,204 @@ describe("tool.read env file permissions", () => { ["environment.ts", false], ] - describe.each(["build", "plan"])("agent=%s", (agentName) => { - test.each(cases)("%s asks=%s", async (filename, shouldAsk) => { - await using tmp = await tmpdir({ - init: (dir) => Bun.write(path.join(dir, filename), "content"), - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const agent = await Agent.get(agentName) - let askedForEnv = false - const ctxWithPermissions = { - ...ctx, - ask: async (req: Omit) => { - for (const pattern of req.patterns) { - const rule = Permission.evaluate(req.permission, pattern, agent.permission) - if (rule.action === "ask" && req.permission === "read") { - askedForEnv = true - } - if (rule.action === "deny") { - throw new Permission.DeniedError({ ruleset: agent.permission }) + for (const agentName of ["build", "plan"] as const) { + describe(`agent=${agentName}`, () => { + for (const [filename, shouldAsk] of cases) { + it.live(`${filename} asks=${shouldAsk}`, () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + yield* put(path.join(dir, filename), "content") + + const asked = yield* provideInstance(dir)( + Effect.gen(function* () { + const agent = yield* Agent.Service + const info = yield* agent.get(agentName) + let asked = false + const next = { + ...ctx, + ask: async (req: Omit) => { + for (const pattern of req.patterns) { + const rule = Permission.evaluate(req.permission, pattern, info.permission) + if (rule.action === "ask" && req.permission === "read") { + asked = true + } + if (rule.action === "deny") { + throw new Permission.DeniedError({ ruleset: info.permission }) + } + } + }, } - } - }, - } - const read = await ReadTool.init() - await read.execute({ filePath: path.join(tmp.path, filename) }, ctxWithPermissions) - expect(askedForEnv).toBe(shouldAsk) - }, - }) + + yield* run({ filePath: path.join(dir, filename) }, next) + return asked + }), + ) + + expect(asked).toBe(shouldAsk) + }), + ) + } }) - }) + } }) describe("tool.read truncation", () => { - test("truncates large file by bytes and sets truncated metadata", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - const base = await Filesystem.readText(path.join(FIXTURES_DIR, "models-api.json")) - const target = 60 * 1024 - const content = base.length >= target ? base : base.repeat(Math.ceil(target / base.length)) - await Filesystem.write(path.join(dir, "large.json"), content) - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const result = await read.execute({ filePath: path.join(tmp.path, "large.json") }, ctx) - expect(result.metadata.truncated).toBe(true) - expect(result.output).toContain("Output capped at") - expect(result.output).toContain("Use offset=") - }, - }) - }) + it.live("truncates large file by bytes and sets truncated metadata", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + const base = yield* load(path.join(FIXTURES_DIR, "models-api.json")) + const target = 60 * 1024 + const content = base.length >= target ? base : base.repeat(Math.ceil(target / base.length)) + yield* put(path.join(dir, "large.json"), content) - test("truncates by line count when limit is specified", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - const lines = Array.from({ length: 100 }, (_, i) => `line${i}`).join("\n") - await Bun.write(path.join(dir, "many-lines.txt"), lines) - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const result = await read.execute({ filePath: path.join(tmp.path, "many-lines.txt"), limit: 10 }, ctx) - expect(result.metadata.truncated).toBe(true) - expect(result.output).toContain("Showing lines 1-10 of 100") - expect(result.output).toContain("Use offset=11") - expect(result.output).toContain("line0") - expect(result.output).toContain("line9") - expect(result.output).not.toContain("line10") - }, - }) - }) + const result = yield* exec(dir, { filePath: path.join(dir, "large.json") }) + expect(result.metadata.truncated).toBe(true) + expect(result.output).toContain("Output capped at") + expect(result.output).toContain("Use offset=") + }), + ) - test("does not truncate small file", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "small.txt"), "hello world") - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const result = await read.execute({ filePath: path.join(tmp.path, "small.txt") }, ctx) - expect(result.metadata.truncated).toBe(false) - expect(result.output).toContain("End of file") - }, - }) - }) + it.live("truncates by line count when limit is specified", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + const lines = Array.from({ length: 100 }, (_, i) => `line${i}`).join("\n") + yield* put(path.join(dir, "many-lines.txt"), lines) - test("respects offset parameter", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - const lines = Array.from({ length: 20 }, (_, i) => `line${i + 1}`).join("\n") - await Bun.write(path.join(dir, "offset.txt"), lines) - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const result = await read.execute({ filePath: path.join(tmp.path, "offset.txt"), offset: 10, limit: 5 }, ctx) - expect(result.output).toContain("10: line10") - expect(result.output).toContain("14: line14") - expect(result.output).not.toContain("9: line10") - expect(result.output).not.toContain("15: line15") - expect(result.output).toContain("line10") - expect(result.output).toContain("line14") - expect(result.output).not.toContain("line0") - expect(result.output).not.toContain("line15") - }, - }) - }) + const result = yield* exec(dir, { filePath: path.join(dir, "many-lines.txt"), limit: 10 }) + expect(result.metadata.truncated).toBe(true) + expect(result.output).toContain("Showing lines 1-10 of 100") + expect(result.output).toContain("Use offset=11") + expect(result.output).toContain("line0") + expect(result.output).toContain("line9") + expect(result.output).not.toContain("line10") + }), + ) - test("throws when offset is beyond end of file", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - const lines = Array.from({ length: 3 }, (_, i) => `line${i + 1}`).join("\n") - await Bun.write(path.join(dir, "short.txt"), lines) - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - await expect( - read.execute({ filePath: path.join(tmp.path, "short.txt"), offset: 4, limit: 5 }, ctx), - ).rejects.toThrow("Offset 4 is out of range for this file (3 lines)") - }, - }) - }) + it.live("does not truncate small file", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + yield* put(path.join(dir, "small.txt"), "hello world") - test("allows reading empty file at default offset", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "empty.txt"), "") - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const result = await read.execute({ filePath: path.join(tmp.path, "empty.txt") }, ctx) - expect(result.metadata.truncated).toBe(false) - expect(result.output).toContain("End of file - total 0 lines") - }, - }) - }) + const result = yield* exec(dir, { filePath: path.join(dir, "small.txt") }) + expect(result.metadata.truncated).toBe(false) + expect(result.output).toContain("End of file") + }), + ) - test("throws when offset > 1 for empty file", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "empty.txt"), "") - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - await expect(read.execute({ filePath: path.join(tmp.path, "empty.txt"), offset: 2 }, ctx)).rejects.toThrow( - "Offset 2 is out of range for this file (0 lines)", - ) - }, - }) - }) + it.live("respects offset parameter", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + const lines = Array.from({ length: 20 }, (_, i) => `line${i + 1}`).join("\n") + yield* put(path.join(dir, "offset.txt"), lines) - test("does not mark final directory page as truncated", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Promise.all( - Array.from({ length: 10 }, (_, i) => Bun.write(path.join(dir, "dir", `file-${i + 1}.txt`), `line${i}`)), - ) - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const result = await read.execute({ filePath: path.join(tmp.path, "dir"), offset: 6, limit: 5 }, ctx) - expect(result.metadata.truncated).toBe(false) - expect(result.output).not.toContain("Showing 5 of 10 entries") - }, - }) - }) + const result = yield* exec(dir, { filePath: path.join(dir, "offset.txt"), offset: 10, limit: 5 }) + expect(result.output).toContain("10: line10") + expect(result.output).toContain("14: line14") + expect(result.output).not.toContain("9: line10") + expect(result.output).not.toContain("15: line15") + expect(result.output).toContain("line10") + expect(result.output).toContain("line14") + expect(result.output).not.toContain("line0") + expect(result.output).not.toContain("line15") + }), + ) - test("truncates long lines", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - const longLine = "x".repeat(3000) - await Bun.write(path.join(dir, "long-line.txt"), longLine) - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const result = await read.execute({ filePath: path.join(tmp.path, "long-line.txt") }, ctx) - expect(result.output).toContain("(line truncated to 2000 chars)") - expect(result.output.length).toBeLessThan(3000) - }, - }) - }) - - test("image files set truncated to false", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - // 1x1 red PNG - const png = Buffer.from( - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==", - "base64", - ) - await Bun.write(path.join(dir, "image.png"), png) - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const result = await read.execute({ filePath: path.join(tmp.path, "image.png") }, ctx) - expect(result.metadata.truncated).toBe(false) - expect(result.attachments).toBeDefined() - expect(result.attachments?.length).toBe(1) - expect(result.attachments?.[0]).not.toHaveProperty("id") - expect(result.attachments?.[0]).not.toHaveProperty("sessionID") - expect(result.attachments?.[0]).not.toHaveProperty("messageID") - }, - }) - }) - - test("large image files are properly attached without error", async () => { - await Instance.provide({ - directory: FIXTURES_DIR, - fn: async () => { - const read = await ReadTool.init() - const result = await read.execute({ filePath: path.join(FIXTURES_DIR, "large-image.png") }, ctx) - expect(result.metadata.truncated).toBe(false) - expect(result.attachments).toBeDefined() - expect(result.attachments?.length).toBe(1) - expect(result.attachments?.[0].type).toBe("file") - expect(result.attachments?.[0]).not.toHaveProperty("id") - expect(result.attachments?.[0]).not.toHaveProperty("sessionID") - expect(result.attachments?.[0]).not.toHaveProperty("messageID") - }, - }) - }) + it.live("throws when offset is beyond end of file", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + const lines = Array.from({ length: 3 }, (_, i) => `line${i + 1}`).join("\n") + yield* put(path.join(dir, "short.txt"), lines) + + const err = yield* fail(dir, { filePath: path.join(dir, "short.txt"), offset: 4, limit: 5 }) + expect(err.message).toContain("Offset 4 is out of range for this file (3 lines)") + }), + ) + + it.live("allows reading empty file at default offset", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + yield* put(path.join(dir, "empty.txt"), "") + + const result = yield* exec(dir, { filePath: path.join(dir, "empty.txt") }) + expect(result.metadata.truncated).toBe(false) + expect(result.output).toContain("End of file - total 0 lines") + }), + ) + + it.live("throws when offset > 1 for empty file", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + yield* put(path.join(dir, "empty.txt"), "") + + const err = yield* fail(dir, { filePath: path.join(dir, "empty.txt"), offset: 2 }) + expect(err.message).toContain("Offset 2 is out of range for this file (0 lines)") + }), + ) + + it.live("does not mark final directory page as truncated", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + yield* Effect.forEach( + Array.from({ length: 10 }, (_, i) => i), + (i) => put(path.join(dir, "dir", `file-${i + 1}.txt`), `line${i}`), + { + concurrency: "unbounded", + }, + ) + + const result = yield* exec(dir, { filePath: path.join(dir, "dir"), offset: 6, limit: 5 }) + expect(result.metadata.truncated).toBe(false) + expect(result.output).not.toContain("Showing 5 of 10 entries") + }), + ) - test(".fbs files (FlatBuffers schema) are read as text, not images", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - // FlatBuffers schema content - const fbsContent = `namespace MyGame; + it.live("truncates long lines", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + yield* put(path.join(dir, "long-line.txt"), "x".repeat(3000)) + + const result = yield* exec(dir, { filePath: path.join(dir, "long-line.txt") }) + expect(result.output).toContain("(line truncated to 2000 chars)") + expect(result.output.length).toBeLessThan(3000) + }), + ) + + it.live("image files set truncated to false", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + const png = Buffer.from( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==", + "base64", + ) + yield* put(path.join(dir, "image.png"), png) + + const result = yield* exec(dir, { filePath: path.join(dir, "image.png") }) + expect(result.metadata.truncated).toBe(false) + expect(result.attachments).toBeDefined() + expect(result.attachments?.length).toBe(1) + expect(result.attachments?.[0]).not.toHaveProperty("id") + expect(result.attachments?.[0]).not.toHaveProperty("sessionID") + expect(result.attachments?.[0]).not.toHaveProperty("messageID") + }), + ) + + it.live("large image files are properly attached without error", () => + Effect.gen(function* () { + const result = yield* exec(FIXTURES_DIR, { filePath: path.join(FIXTURES_DIR, "large-image.png") }) + expect(result.metadata.truncated).toBe(false) + expect(result.attachments).toBeDefined() + expect(result.attachments?.length).toBe(1) + expect(result.attachments?.[0].type).toBe("file") + expect(result.attachments?.[0]).not.toHaveProperty("id") + expect(result.attachments?.[0]).not.toHaveProperty("sessionID") + expect(result.attachments?.[0]).not.toHaveProperty("messageID") + }), + ) + + it.live(".fbs files (FlatBuffers schema) are read as text, not images", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + const fbs = `namespace MyGame; table Monster { pos:Vec3; @@ -468,79 +417,52 @@ table Monster { } root_type Monster;` - await Bun.write(path.join(dir, "schema.fbs"), fbsContent) - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const result = await read.execute({ filePath: path.join(tmp.path, "schema.fbs") }, ctx) - // Should be read as text, not as image - expect(result.attachments).toBeUndefined() - expect(result.output).toContain("namespace MyGame") - expect(result.output).toContain("table Monster") - }, - }) - }) + yield* put(path.join(dir, "schema.fbs"), fbs) + + const result = yield* exec(dir, { filePath: path.join(dir, "schema.fbs") }) + expect(result.attachments).toBeUndefined() + expect(result.output).toContain("namespace MyGame") + expect(result.output).toContain("table Monster") + }), + ) }) describe("tool.read loaded instructions", () => { - test("loads AGENTS.md from parent directory and includes in metadata", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "subdir", "AGENTS.md"), "# Test Instructions\nDo something special.") - await Bun.write(path.join(dir, "subdir", "nested", "test.txt"), "test content") - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - const result = await read.execute({ filePath: path.join(tmp.path, "subdir", "nested", "test.txt") }, ctx) - expect(result.output).toContain("test content") - expect(result.output).toContain("system-reminder") - expect(result.output).toContain("Test Instructions") - expect(result.metadata.loaded).toBeDefined() - expect(result.metadata.loaded).toContain(path.join(tmp.path, "subdir", "AGENTS.md")) - }, - }) - }) + it.live("loads AGENTS.md from parent directory and includes in metadata", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + yield* put(path.join(dir, "subdir", "AGENTS.md"), "# Test Instructions\nDo something special.") + yield* put(path.join(dir, "subdir", "nested", "test.txt"), "test content") + + const result = yield* exec(dir, { filePath: path.join(dir, "subdir", "nested", "test.txt") }) + expect(result.output).toContain("test content") + expect(result.output).toContain("system-reminder") + expect(result.output).toContain("Test Instructions") + expect(result.metadata.loaded).toBeDefined() + expect(result.metadata.loaded).toContain(path.join(dir, "subdir", "AGENTS.md")) + }), + ) }) describe("tool.read binary detection", () => { - test("rejects text extension files with null bytes", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - const bytes = Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x77, 0x6f, 0x72, 0x6c, 0x64]) - await Bun.write(path.join(dir, "null-byte.txt"), bytes) - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - await expect(read.execute({ filePath: path.join(tmp.path, "null-byte.txt") }, ctx)).rejects.toThrow( - "Cannot read binary file", - ) - }, - }) - }) + it.live("rejects text extension files with null bytes", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + const bytes = Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x77, 0x6f, 0x72, 0x6c, 0x64]) + yield* put(path.join(dir, "null-byte.txt"), bytes) - test("rejects known binary extensions", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Bun.write(path.join(dir, "module.wasm"), "not really wasm") - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const read = await ReadTool.init() - await expect(read.execute({ filePath: path.join(tmp.path, "module.wasm") }, ctx)).rejects.toThrow( - "Cannot read binary file", - ) - }, - }) - }) + const err = yield* fail(dir, { filePath: path.join(dir, "null-byte.txt") }) + expect(err.message).toContain("Cannot read binary file") + }), + ) + + it.live("rejects known binary extensions", () => + Effect.gen(function* () { + const dir = yield* tmpdirScoped() + yield* put(path.join(dir, "module.wasm"), "not really wasm") + + const err = yield* fail(dir, { filePath: path.join(dir, "module.wasm") }) + expect(err.message).toContain("Cannot read binary file") + }), + ) }) From ef58bfb6f9bd51875e80675510442df32bd35d41 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 4 Apr 2026 23:07:01 +0000 Subject: [PATCH 082/616] chore: generate --- packages/sdk/openapi.json | 183 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 735c34c8acea..02370f75fc0b 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -1220,6 +1220,189 @@ ] } }, + "/experimental/console": { + "get": { + "operationId": "experimental.console.get", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + } + ], + "summary": "Get active Console provider metadata", + "description": "Get the active Console org name and the set of provider IDs managed by that Console org.", + "responses": { + "200": { + "description": "Active Console provider metadata", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "consoleManagedProviders": { + "type": "array", + "items": { + "type": "string" + } + }, + "activeOrgName": { + "type": "string" + } + }, + "required": ["consoleManagedProviders"] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.console.get({\n ...\n})" + } + ] + } + }, + "/experimental/console/orgs": { + "get": { + "operationId": "experimental.console.listOrgs", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + } + ], + "summary": "List switchable Console orgs", + "description": "Get the available Console orgs across logged-in accounts, including the current active org.", + "responses": { + "200": { + "description": "Switchable Console orgs", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "orgs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "accountID": { + "type": "string" + }, + "accountEmail": { + "type": "string" + }, + "accountUrl": { + "type": "string" + }, + "orgID": { + "type": "string" + }, + "orgName": { + "type": "string" + }, + "active": { + "type": "boolean" + } + }, + "required": ["accountID", "accountEmail", "accountUrl", "orgID", "orgName", "active"] + } + } + }, + "required": ["orgs"] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.console.listOrgs({\n ...\n})" + } + ] + } + }, + "/experimental/console/switch": { + "post": { + "operationId": "experimental.console.switchOrg", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + } + ], + "summary": "Switch active Console org", + "description": "Persist a new active Console account/org selection for the current local OpenCode state.", + "responses": { + "200": { + "description": "Switch success", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "accountID": { + "type": "string" + }, + "orgID": { + "type": "string" + } + }, + "required": ["accountID", "orgID"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.console.switchOrg({\n ...\n})" + } + ] + } + }, "/experimental/tool/ids": { "get": { "operationId": "tool.ids", From 7203b1a2732f419c03d947bc0dbe08f00369d4f8 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 4 Apr 2026 19:25:37 -0400 Subject: [PATCH 083/616] fix(tui): only show org switch affordances when useful (#21054) --- packages/opencode/src/cli/cmd/tui/app.tsx | 30 +++++++++++-------- .../cli/cmd/tui/component/prompt/index.tsx | 9 +++++- packages/opencode/src/config/config.ts | 1 + packages/opencode/src/config/console-state.ts | 2 ++ .../src/server/routes/experimental.ts | 6 +++- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 8ce7382929af..6b2633c371f3 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -630,19 +630,23 @@ function App(props: { onSnapshot?: () => Promise }) { }, category: "Provider", }, - { - title: "Switch org", - value: "console.org.switch", - suggested: Boolean(sync.data.console_state.activeOrgName), - slash: { - name: "org", - aliases: ["orgs", "switch-org"], - }, - onSelect: () => { - dialog.replace(() => ) - }, - category: "Provider", - }, + ...(sync.data.console_state.switchableOrgCount > 1 + ? [ + { + title: "Switch org", + value: "console.org.switch", + suggested: Boolean(sync.data.console_state.activeOrgName), + slash: { + name: "org", + aliases: ["orgs", "switch-org"], + }, + onSelect: () => { + dialog.replace(() => ) + }, + category: "Provider", + }, + ] + : []), { title: "View status", keybind: "status_view", diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 55bf1d563066..5123cea56754 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -96,6 +96,7 @@ export function Prompt(props: PromptProps) { const shell = createMemo(() => props.placeholders?.shell ?? []) const [auto, setAuto] = createSignal() const activeOrgName = createMemo(() => sync.data.console_state.activeOrgName) + const canSwitchOrgs = createMemo(() => sync.data.console_state.switchableOrgCount > 1) const currentProviderLabel = createMemo(() => { const current = local.model.current() const provider = local.model.parsed().provider @@ -1118,7 +1119,13 @@ export function Prompt(props: PromptProps) { {props.right} - command.trigger("console.org.switch")}> + { + if (!canSwitchOrgs()) return + command.trigger("console.org.switch") + }} + > {`${CONSOLE_MANAGED_ICON} ${activeOrgName()}`} diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 83e677bcb791..b11ae83192ce 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1469,6 +1469,7 @@ export namespace Config { consoleState: { consoleManagedProviders: Array.from(consoleManagedProviders), activeOrgName, + switchableOrgCount: 0, }, } }) diff --git a/packages/opencode/src/config/console-state.ts b/packages/opencode/src/config/console-state.ts index a5d1f6d2699f..cf96a4e305a3 100644 --- a/packages/opencode/src/config/console-state.ts +++ b/packages/opencode/src/config/console-state.ts @@ -3,6 +3,7 @@ import z from "zod" export const ConsoleState = z.object({ consoleManagedProviders: z.array(z.string()), activeOrgName: z.string().optional(), + switchableOrgCount: z.number().int().nonnegative(), }) export type ConsoleState = z.infer @@ -10,4 +11,5 @@ export type ConsoleState = z.infer export const emptyConsoleState: ConsoleState = { consoleManagedProviders: [], activeOrgName: undefined, + switchableOrgCount: 0, } diff --git a/packages/opencode/src/server/routes/experimental.ts b/packages/opencode/src/server/routes/experimental.ts index a4b1f4d084d3..c673393d0e36 100644 --- a/packages/opencode/src/server/routes/experimental.ts +++ b/packages/opencode/src/server/routes/experimental.ts @@ -54,7 +54,11 @@ export const ExperimentalRoutes = lazy(() => }, }), async (c) => { - return c.json(await Config.getConsoleState()) + const [consoleState, groups] = await Promise.all([Config.getConsoleState(), Account.orgsByAccount()]) + return c.json({ + ...consoleState, + switchableOrgCount: groups.reduce((count, group) => count + group.orgs.length, 0), + }) }, ) .get( From 4d8c3385bcca887367dcca08d4c44546e06afa9c Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 4 Apr 2026 23:26:37 +0000 Subject: [PATCH 084/616] chore: generate --- packages/sdk/js/src/v2/gen/types.gen.ts | 1 + packages/sdk/openapi.json | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 4c348573f6d1..72e549e485ab 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2670,6 +2670,7 @@ export type ExperimentalConsoleGetResponses = { 200: { consoleManagedProviders: Array activeOrgName?: string + switchableOrgCount: number } } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 02370f75fc0b..1aa4010e7ade 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -1257,9 +1257,14 @@ }, "activeOrgName": { "type": "string" + }, + "switchableOrgCount": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 } }, - "required": ["consoleManagedProviders"] + "required": ["consoleManagedProviders", "switchableOrgCount"] } } } From af787af21de9cd76dfacafceefd5e5932436d9c4 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sat, 4 Apr 2026 16:40:28 -0700 Subject: [PATCH 085/616] test: add regression test for double counting bug (#21053) --- .../opencode/test/session/compaction.test.ts | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 0dac255b10f2..799bb3e2aeb1 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -1080,7 +1080,7 @@ describe("session.getUsage", () => { expect(result.tokens.cache.read).toBe(200) }) - test("handles reasoning tokens", () => { + test("separates reasoning tokens from output tokens", () => { const model = createModel({ context: 100_000, output: 32_000 }) const result = Session.getUsage({ model, @@ -1092,7 +1092,35 @@ describe("session.getUsage", () => { }, }) + expect(result.tokens.input).toBe(1000) + expect(result.tokens.output).toBe(400) expect(result.tokens.reasoning).toBe(100) + expect(result.tokens.total).toBe(1500) + }) + + test("does not double count reasoning tokens in cost", () => { + const model = createModel({ + context: 100_000, + output: 32_000, + cost: { + input: 0, + output: 15, + cache: { read: 0, write: 0 }, + }, + }) + const result = Session.getUsage({ + model, + usage: { + inputTokens: 0, + outputTokens: 1_000_000, + totalTokens: 1_000_000, + reasoningTokens: 250_000, + }, + }) + + expect(result.tokens.output).toBe(750_000) + expect(result.tokens.reasoning).toBe(250_000) + expect(result.cost).toBe(15) }) test("handles undefined optional values gracefully", () => { From c1c92862e6418f24eb6652f04c60b33599848f33 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 5 Apr 2026 00:35:38 -0400 Subject: [PATCH 086/616] doc: udpate doc --- packages/web/src/content/docs/zen.mdx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 847654372f67..c16fc5270e28 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -94,8 +94,6 @@ You can also access our models through the following API endpoints. | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -122,8 +120,6 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | | Qwen3.6 Plus Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | @@ -169,8 +165,6 @@ Credit card fees are passed along at cost (4.4% + $0.30 per transaction); we don The free models: - MiniMax M2.5 Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. -- MiMo V2 Pro Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. -- MiMo V2 Omni Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Qwen3.6 Plus Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Nemotron 3 Super Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Big Pickle is a stealth model that's free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. @@ -218,8 +212,6 @@ All our models are hosted in the US. Our providers follow a zero-retention polic - Big Pickle: During its free period, collected data may be used to improve the model. - MiniMax M2.5 Free: During its free period, collected data may be used to improve the model. -- MiMo V2 Pro Free: During its free period, collected data may be used to improve the model. -- MiMo V2 Omni Free: During its free period, collected data may be used to improve the model. - Qwen3.6 Plus Free: During its free period, collected data may be used to improve the model. - Nemotron 3 Super Free: During its free period, collected data may be used to improve the model. - OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). From 67e26a502f4c8ca69e645cda8893fef3f1b7a9fa Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sat, 4 Apr 2026 21:55:22 -0700 Subject: [PATCH 087/616] tweak: add newline between and first line of read tool output to prevent confusion (#21070) --- packages/opencode/src/tool/read.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index 366993020ba1..0b44c7ad5a79 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -185,7 +185,7 @@ export const ReadTool = Tool.defineEffect( ) } - let output = [`${filepath}`, `file`, ""].join("\n") + let output = [`${filepath}`, `file`, "" + "\n"].join("\n") output += file.raw.map((line, i) => `${i + file.offset}: ${line}`).join("\n") const last = file.offset + file.raw.length - 1 From 8a3da247eb8356574f2f8481d7c28ff1888736c1 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Mon, 6 Apr 2026 10:26:40 +1000 Subject: [PATCH 088/616] fix(plugin): parse package specifiers with npm-package-arg and sanitize win32 cache paths (#21135) --- bun.lock | 2 + packages/opencode/package.json | 2 + packages/opencode/src/npm/index.ts | 8 +- packages/opencode/src/plugin/shared.ts | 28 +++++-- packages/opencode/test/npm.test.ts | 18 ++++ packages/opencode/test/plugin/shared.test.ts | 88 ++++++++++++++++++++ 6 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 packages/opencode/test/npm.test.ts create mode 100644 packages/opencode/test/plugin/shared.test.ts diff --git a/bun.lock b/bun.lock index 3550c67441cb..a7505304a8b1 100644 --- a/bun.lock +++ b/bun.lock @@ -370,6 +370,7 @@ "jsonc-parser": "3.3.1", "mime-types": "3.0.2", "minimatch": "10.0.3", + "npm-package-arg": "13.0.2", "open": "10.1.2", "opencode-gitlab-auth": "2.0.1", "opencode-poe-auth": "0.0.1", @@ -411,6 +412,7 @@ "@types/bun": "catalog:", "@types/cross-spawn": "catalog:", "@types/mime-types": "3.0.1", + "@types/npm-package-arg": "6.1.4", "@types/npmcli__arborist": "6.3.3", "@types/semver": "^7.5.8", "@types/turndown": "5.0.5", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 24f296e73f47..e92dd991cc25 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -47,6 +47,7 @@ "@types/bun": "catalog:", "@types/cross-spawn": "catalog:", "@types/mime-types": "3.0.1", + "@types/npm-package-arg": "6.1.4", "@types/npmcli__arborist": "6.3.3", "@types/semver": "^7.5.8", "@types/turndown": "5.0.5", @@ -128,6 +129,7 @@ "jsonc-parser": "3.3.1", "mime-types": "3.0.2", "minimatch": "10.0.3", + "npm-package-arg": "13.0.2", "open": "10.1.2", "opencode-gitlab-auth": "2.0.1", "opencode-poe-auth": "0.0.1", diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index 69bb2ca5284e..3568ff20e245 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -11,6 +11,7 @@ import { Arborist } from "@npmcli/arborist" export namespace Npm { const log = Log.create({ service: "npm" }) + const illegal = process.platform === "win32" ? new Set(["<", ">", ":", '"', "|", "?", "*"]) : undefined export const InstallFailedError = NamedError.create( "NpmInstallFailedError", @@ -19,8 +20,13 @@ export namespace Npm { }), ) + export function sanitize(pkg: string) { + if (!illegal) return pkg + return Array.from(pkg, (char) => (illegal.has(char) || char.charCodeAt(0) < 32 ? "_" : char)).join("") + } + function directory(pkg: string) { - return path.join(Global.Path.cache, "packages", pkg) + return path.join(Global.Path.cache, "packages", sanitize(pkg)) } function resolveEntryPoint(name: string, dir: string) { diff --git a/packages/opencode/src/plugin/shared.ts b/packages/opencode/src/plugin/shared.ts index f92520d05dc2..6cda49786bc9 100644 --- a/packages/opencode/src/plugin/shared.ts +++ b/packages/opencode/src/plugin/shared.ts @@ -1,5 +1,6 @@ import path from "path" import { fileURLToPath, pathToFileURL } from "url" +import npa from "npm-package-arg" import semver from "semver" import { Npm } from "@/npm" import { Filesystem } from "@/util/filesystem" @@ -12,11 +13,24 @@ export function isDeprecatedPlugin(spec: string) { return DEPRECATED_PLUGIN_PACKAGES.some((pkg) => spec.includes(pkg)) } +function parse(spec: string) { + try { + return npa(spec) + } catch {} +} + export function parsePluginSpecifier(spec: string) { - const lastAt = spec.lastIndexOf("@") - const pkg = lastAt > 0 ? spec.substring(0, lastAt) : spec - const version = lastAt > 0 ? spec.substring(lastAt + 1) : "latest" - return { pkg, version } + const hit = parse(spec) + if (hit?.type === "alias" && !hit.name) { + const sub = (hit as npa.AliasResult).subSpec + if (sub?.name) { + const version = !sub.rawSpec || sub.rawSpec === "*" ? "latest" : sub.rawSpec + return { pkg: sub.name, version } + } + } + if (!hit?.name) return { pkg: spec, version: "" } + if (hit.raw === hit.name) return { pkg: hit.name, version: "latest" } + return { pkg: hit.name, version: hit.rawSpec } } export type PluginSource = "file" | "npm" @@ -190,9 +204,11 @@ export async function checkPluginCompatibility(target: string, opencodeVersion: } } -export async function resolvePluginTarget(spec: string, parsed = parsePluginSpecifier(spec)) { +export async function resolvePluginTarget(spec: string) { if (isPathPluginSpec(spec)) return resolvePathPluginTarget(spec) - const result = await Npm.add(parsed.pkg + "@" + parsed.version) + const hit = parse(spec) + const pkg = hit?.name && hit.raw === hit.name ? `${hit.name}@latest` : spec + const result = await Npm.add(pkg) return result.directory } diff --git a/packages/opencode/test/npm.test.ts b/packages/opencode/test/npm.test.ts new file mode 100644 index 000000000000..61e3ca6ddf02 --- /dev/null +++ b/packages/opencode/test/npm.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, test } from "bun:test" +import { Npm } from "../src/npm" + +const win = process.platform === "win32" + +describe("Npm.sanitize", () => { + test("keeps normal scoped package specs unchanged", () => { + expect(Npm.sanitize("@opencode/acme")).toBe("@opencode/acme") + expect(Npm.sanitize("@opencode/acme@1.0.0")).toBe("@opencode/acme@1.0.0") + expect(Npm.sanitize("prettier")).toBe("prettier") + }) + + test("handles git https specs", () => { + const spec = "acme@git+https://github.com/opencode/acme.git" + const expected = win ? "acme@git+https_//github.com/opencode/acme.git" : spec + expect(Npm.sanitize(spec)).toBe(expected) + }) +}) diff --git a/packages/opencode/test/plugin/shared.test.ts b/packages/opencode/test/plugin/shared.test.ts new file mode 100644 index 000000000000..98475b02f469 --- /dev/null +++ b/packages/opencode/test/plugin/shared.test.ts @@ -0,0 +1,88 @@ +import { describe, expect, test } from "bun:test" +import { parsePluginSpecifier } from "../../src/plugin/shared" + +describe("parsePluginSpecifier", () => { + test("parses standard npm package without version", () => { + expect(parsePluginSpecifier("acme")).toEqual({ + pkg: "acme", + version: "latest", + }) + }) + + test("parses standard npm package with version", () => { + expect(parsePluginSpecifier("acme@1.0.0")).toEqual({ + pkg: "acme", + version: "1.0.0", + }) + }) + + test("parses scoped npm package without version", () => { + expect(parsePluginSpecifier("@opencode/acme")).toEqual({ + pkg: "@opencode/acme", + version: "latest", + }) + }) + + test("parses scoped npm package with version", () => { + expect(parsePluginSpecifier("@opencode/acme@1.0.0")).toEqual({ + pkg: "@opencode/acme", + version: "1.0.0", + }) + }) + + test("parses package with git+https url", () => { + expect(parsePluginSpecifier("acme@git+https://github.com/opencode/acme.git")).toEqual({ + pkg: "acme", + version: "git+https://github.com/opencode/acme.git", + }) + }) + + test("parses scoped package with git+https url", () => { + expect(parsePluginSpecifier("@opencode/acme@git+https://github.com/opencode/acme.git")).toEqual({ + pkg: "@opencode/acme", + version: "git+https://github.com/opencode/acme.git", + }) + }) + + test("parses package with git+ssh url containing another @", () => { + expect(parsePluginSpecifier("acme@git+ssh://git@github.com/opencode/acme.git")).toEqual({ + pkg: "acme", + version: "git+ssh://git@github.com/opencode/acme.git", + }) + }) + + test("parses scoped package with git+ssh url containing another @", () => { + expect(parsePluginSpecifier("@opencode/acme@git+ssh://git@github.com/opencode/acme.git")).toEqual({ + pkg: "@opencode/acme", + version: "git+ssh://git@github.com/opencode/acme.git", + }) + }) + + test("parses unaliased git+ssh url", () => { + expect(parsePluginSpecifier("git+ssh://git@github.com/opencode/acme.git")).toEqual({ + pkg: "git+ssh://git@github.com/opencode/acme.git", + version: "", + }) + }) + + test("parses npm alias using the alias name", () => { + expect(parsePluginSpecifier("acme@npm:@opencode/acme@1.0.0")).toEqual({ + pkg: "acme", + version: "npm:@opencode/acme@1.0.0", + }) + }) + + test("parses bare npm protocol specifier using the target package", () => { + expect(parsePluginSpecifier("npm:@opencode/acme@1.0.0")).toEqual({ + pkg: "@opencode/acme", + version: "1.0.0", + }) + }) + + test("parses unversioned npm protocol specifier", () => { + expect(parsePluginSpecifier("npm:@opencode/acme")).toEqual({ + pkg: "@opencode/acme", + version: "latest", + }) + }) +}) From a164dc783fef9988126d9b3c59d47afa5a548cdf Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 6 Apr 2026 01:18:03 +0000 Subject: [PATCH 089/616] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index e1e8e12b7076..e09f3cf6d18d 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-0jwPCu2Lod433GPQLHN8eEkhfpPviDFfkFJmuvkRdlE=", - "aarch64-linux": "sha256-Qi0IkGkaIBKZsPLTO8kaTbCVL0cEfVOm/Y/6VUVI9TY=", - "aarch64-darwin": "sha256-1eZBBLgYVkjg5RYN/etR1Mb5UjU3VelElBB5ug5hQdc=", - "x86_64-darwin": "sha256-jdXgA+kZb/foFHR40UiPif6rsA2GDVCCVHnJR3jBUGI=" + "x86_64-linux": "sha256-LRhPPrOKCGUSCEWTpAxPdWKTKVNkg82WrvD25cP3jts=", + "aarch64-linux": "sha256-sbNxkil47n+B7v6ds5EYFybLytXUyRlu0Cpka0ZmDx4=", + "aarch64-darwin": "sha256-5+99gtpIHGygMW3VBAexNhmaORgI8LCxPk/Gf1fW/ds=", + "x86_64-darwin": "sha256-LqnvZGGnQaRxIoowOr5gf6lFgDhbgQhVPiAcRTtU6fE=" } } From ce5f4459ae24a4fbf0e468a56d1039b1bdddb69f Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Mon, 6 Apr 2026 04:14:11 +0200 Subject: [PATCH 090/616] feat(tui): make the mouse disablable (#6824, #7926) (#13748) --- packages/opencode/src/cli/cmd/tui/app.tsx | 3 +++ packages/opencode/src/config/tui-schema.ts | 1 + packages/opencode/src/flag/flag.ts | 1 + packages/web/src/content/docs/cli.mdx | 1 + packages/web/src/content/docs/config.mdx | 5 ++--- packages/web/src/content/docs/tui.mdx | 4 +++- 6 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 6b2633c371f3..d643c0704f4c 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -125,6 +125,8 @@ import type { EventSource } from "./context/sdk" import { DialogVariant } from "./component/dialog-variant" function rendererConfig(_config: TuiConfig.Info): CliRendererConfig { + const mouseEnabled = !Flag.OPENCODE_DISABLE_MOUSE && (_config.mouse ?? true) + return { externalOutputMode: "passthrough", targetFps: 60, @@ -133,6 +135,7 @@ function rendererConfig(_config: TuiConfig.Info): CliRendererConfig { useKittyKeyboard: { events: process.platform === "win32" }, autoFocus: false, openConsoleOnError: false, + useMouse: mouseEnabled, consoleOptions: { keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }], onCopySelection: (text) => { diff --git a/packages/opencode/src/config/tui-schema.ts b/packages/opencode/src/config/tui-schema.ts index b126d3c96a42..a373b4d8009b 100644 --- a/packages/opencode/src/config/tui-schema.ts +++ b/packages/opencode/src/config/tui-schema.ts @@ -22,6 +22,7 @@ export const TuiOptions = z.object({ .enum(["auto", "stacked"]) .optional() .describe("Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column"), + mouse: z.boolean().optional().describe("Enable or disable mouse capture (default: true)"), }) export const TuiInfo = z diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 1ac52dd17fa1..739009502b17 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -31,6 +31,7 @@ export namespace Flag { export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS") export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT") export const OPENCODE_DISABLE_MODELS_FETCH = truthy("OPENCODE_DISABLE_MODELS_FETCH") + export const OPENCODE_DISABLE_MOUSE = truthy("OPENCODE_DISABLE_MOUSE") export const OPENCODE_DISABLE_CLAUDE_CODE = truthy("OPENCODE_DISABLE_CLAUDE_CODE") export const OPENCODE_DISABLE_CLAUDE_CODE_PROMPT = OPENCODE_DISABLE_CLAUDE_CODE || truthy("OPENCODE_DISABLE_CLAUDE_CODE_PROMPT") diff --git a/packages/web/src/content/docs/cli.mdx b/packages/web/src/content/docs/cli.mdx index e2ba2404de94..579038ad03df 100644 --- a/packages/web/src/content/docs/cli.mdx +++ b/packages/web/src/content/docs/cli.mdx @@ -573,6 +573,7 @@ OpenCode can be configured using environment variables. | `OPENCODE_DISABLE_CLAUDE_CODE_PROMPT` | boolean | Disable reading `~/.claude/CLAUDE.md` | | `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS` | boolean | Disable loading `.claude/skills` | | `OPENCODE_DISABLE_MODELS_FETCH` | boolean | Disable fetching models from remote sources | +| `OPENCODE_DISABLE_MOUSE` | boolean | Disable mouse capture in the TUI | | `OPENCODE_FAKE_VCS` | string | Fake VCS provider for testing purposes | | `OPENCODE_DISABLE_FILETIME_CHECK` | boolean | Disable file time checking for optimization | | `OPENCODE_CLIENT` | string | Client identifier (defaults to `cli`) | diff --git a/packages/web/src/content/docs/config.mdx b/packages/web/src/content/docs/config.mdx index 39f3cd8ff03c..52ee1da0a383 100644 --- a/packages/web/src/content/docs/config.mdx +++ b/packages/web/src/content/docs/config.mdx @@ -272,7 +272,8 @@ Use a dedicated `tui.json` (or `tui.jsonc`) file for TUI-specific settings. "scroll_acceleration": { "enabled": true }, - "diff_style": "auto" + "diff_style": "auto", + "mouse": true } ``` @@ -280,8 +281,6 @@ Use `OPENCODE_TUI_CONFIG` to point to a custom TUI config file. Legacy `theme`, `keybinds`, and `tui` keys in `opencode.json` are deprecated and automatically migrated when possible. -[Learn more about TUI configuration here](/docs/tui#configure). - --- ### Server diff --git a/packages/web/src/content/docs/tui.mdx b/packages/web/src/content/docs/tui.mdx index 6dfa7b3125a3..e89fb4af39ef 100644 --- a/packages/web/src/content/docs/tui.mdx +++ b/packages/web/src/content/docs/tui.mdx @@ -368,7 +368,8 @@ You can customize TUI behavior through `tui.json` (or `tui.jsonc`). "scroll_acceleration": { "enabled": true }, - "diff_style": "auto" + "diff_style": "auto", + "mouse": true } ``` @@ -381,6 +382,7 @@ This is separate from `opencode.json`, which configures server/runtime behavior. - `scroll_acceleration.enabled` - Enable macOS-style scroll acceleration for smooth, natural scrolling. When enabled, scroll speed increases with rapid scrolling gestures and stays precise for slower movements. **This setting takes precedence over `scroll_speed` and overrides it when enabled.** - `scroll_speed` - Controls how fast the TUI scrolls when using scroll commands (minimum: `0.001`, supports decimal values). Defaults to `3`. **Note: This is ignored if `scroll_acceleration.enabled` is set to `true`.** - `diff_style` - Controls diff rendering. `"auto"` adapts to terminal width, `"stacked"` always shows a single-column layout. +- `mouse` - Enable or disable mouse capture in the TUI (default: `true`). When disabled, the terminal's native mouse selection/scrolling behavior is preserved. Use `OPENCODE_TUI_CONFIG` to load a custom TUI config path. From 1c2e695fc4a3b31012007a6dea561f12cb297bc5 Mon Sep 17 00:00:00 2001 From: George Harker Date: Sun, 5 Apr 2026 19:29:34 -0700 Subject: [PATCH 091/616] fix(core): implement proper configOptions for acp (#21134) --- packages/opencode/src/acp/agent.ts | 84 ++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 96a97be75296..6e87e7642d65 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -21,6 +21,9 @@ import { type Role, type SessionInfo, type SetSessionModelRequest, + type SessionConfigOption, + type SetSessionConfigOptionRequest, + type SetSessionConfigOptionResponse, type SetSessionModeRequest, type SetSessionModeResponse, type ToolCallContent, @@ -601,6 +604,7 @@ export namespace ACP { return { sessionId, + configOptions: load.configOptions, models: load.models, modes: load.modes, _meta: load._meta, @@ -660,6 +664,11 @@ export namespace ACP { result.modes.currentModeId = lastUser.agent this.sessionManager.setMode(sessionId, lastUser.agent) } + result.configOptions = buildConfigOptions({ + currentModelId: result.models.currentModelId, + availableModels: result.models.availableModels, + modes: result.modes, + }) } for (const msg of messages ?? []) { @@ -1266,6 +1275,11 @@ export namespace ACP { availableModels, }, modes, + configOptions: buildConfigOptions({ + currentModelId: formatModelIdWithVariant(model, currentVariant, availableVariants, true), + availableModels, + modes, + }), _meta: buildVariantMeta({ model, variant: this.sessionManager.getVariant(sessionId), @@ -1305,6 +1319,44 @@ export namespace ACP { this.sessionManager.setMode(params.sessionId, params.modeId) } + async setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise { + const session = this.sessionManager.get(params.sessionId) + const providers = await this.sdk.config + .providers({ directory: session.cwd }, { throwOnError: true }) + .then((x) => x.data!.providers) + const entries = sortProvidersByName(providers) + + if (params.configId === "model") { + if (typeof params.value !== "string") throw RequestError.invalidParams("model value must be a string") + const selection = parseModelSelection(params.value, providers) + this.sessionManager.setModel(session.id, selection.model) + this.sessionManager.setVariant(session.id, selection.variant) + } else if (params.configId === "mode") { + if (typeof params.value !== "string") throw RequestError.invalidParams("mode value must be a string") + const availableModes = await this.loadAvailableModes(session.cwd) + if (!availableModes.some((mode) => mode.id === params.value)) { + throw RequestError.invalidParams(JSON.stringify({ error: `Mode not found: ${params.value}` })) + } + this.sessionManager.setMode(session.id, params.value) + } else { + throw RequestError.invalidParams(JSON.stringify({ error: `Unknown config option: ${params.configId}` })) + } + + const updatedSession = this.sessionManager.get(session.id) + const model = updatedSession.model ?? (await defaultModel(this.config, session.cwd)) + const availableVariants = modelVariantsFromProviders(entries, model) + const currentModelId = formatModelIdWithVariant(model, updatedSession.variant, availableVariants, true) + const availableModels = buildAvailableModels(entries, { includeVariants: true }) + const modeState = await this.resolveModeState(session.cwd, session.id) + const modes = modeState.currentModeId + ? { availableModes: modeState.availableModes, currentModeId: modeState.currentModeId } + : undefined + + return { + configOptions: buildConfigOptions({ currentModelId, availableModels, modes }), + } + } + async prompt(params: PromptRequest) { const sessionID = params.sessionId const session = this.sessionManager.get(sessionID) @@ -1760,4 +1812,36 @@ export namespace ACP { return { model: parsed, variant: undefined } } + + function buildConfigOptions(input: { + currentModelId: string + availableModels: ModelOption[] + modes?: { availableModes: ModeOption[]; currentModeId: string } | undefined + }): SessionConfigOption[] { + const options: SessionConfigOption[] = [ + { + id: "model", + name: "Model", + category: "model", + type: "select", + currentValue: input.currentModelId, + options: input.availableModels.map((m) => ({ value: m.modelId, name: m.name })), + }, + ] + if (input.modes) { + options.push({ + id: "mode", + name: "Session Mode", + category: "mode", + type: "select", + currentValue: input.modes.currentModeId, + options: input.modes.availableModes.map((m) => ({ + value: m.id, + name: m.name, + ...(m.description ? { description: m.description } : {}), + })), + }) + } + return options + } } From e2897339987546d87014fd3db31034aca1984b54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20Steenhuis?= Date: Mon, 6 Apr 2026 04:34:53 +0200 Subject: [PATCH 092/616] fix: pass both 'openai' and 'azure' providerOptions keys for @ai-sdk/azure (#20272) Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/opencode/src/provider/transform.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index c402238685f9..c1617da40bb9 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -936,6 +936,12 @@ export namespace ProviderTransform { } const key = sdkKey(model.api.npm) ?? model.providerID + // @ai-sdk/azure delegates to OpenAIChatLanguageModel which reads from + // providerOptions["openai"], but OpenAIResponsesLanguageModel checks + // "azure" first. Pass both so model options work on either code path. + if (model.api.npm === "@ai-sdk/azure") { + return { openai: options, azure: options } + } return { [key]: options } } From 953f0f4edcd1be70a0f0bdecd286f66761916a25 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Mon, 6 Apr 2026 12:38:35 +1000 Subject: [PATCH 093/616] fix(tui): default Ctrl+Z to undo on Windows (#21138) --- packages/opencode/src/cli/cmd/tui/app.tsx | 1 + .../tui/feature-plugins/home/tips-view.tsx | 4 +- packages/opencode/src/config/tui.ts | 10 +++- packages/opencode/test/config/tui.test.ts | 48 +++++++++++++++++++ 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index d643c0704f4c..93a5da037f32 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -761,6 +761,7 @@ function App(props: { onSnapshot?: () => Promise }) { keybind: "terminal_suspend", category: "System", hidden: true, + enabled: tuiConfig.keybinds?.terminal_suspend !== "none", onSelect: () => { process.once("SIGCONT", () => { renderer.resume() diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx index 08e429617f05..a87e4ed2b73d 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx @@ -148,5 +148,7 @@ const TIPS = [ "Use {highlight}/review{/highlight} to review uncommitted changes, branches, or PRs", "Run {highlight}/help{/highlight} or {highlight}Ctrl+X H{/highlight} to show the help dialog", "Use {highlight}/rename{/highlight} to rename the current session", - "Press {highlight}Ctrl+Z{/highlight} to suspend the terminal and return to your shell", + ...(process.platform === "win32" + ? ["Press {highlight}Ctrl+Z{/highlight} to undo changes in your prompt"] + : ["Press {highlight}Ctrl+Z{/highlight} to suspend the terminal and return to your shell"]), ] diff --git a/packages/opencode/src/config/tui.ts b/packages/opencode/src/config/tui.ts index adfb3c781069..fa2022482d25 100644 --- a/packages/opencode/src/config/tui.ts +++ b/packages/opencode/src/config/tui.ts @@ -111,7 +111,15 @@ export namespace TuiConfig { } } - acc.result.keybinds = Config.Keybinds.parse(acc.result.keybinds ?? {}) + const keybinds = { ...(acc.result.keybinds ?? {}) } + if (process.platform === "win32") { + // Native Windows terminals do not support POSIX suspend, so prefer prompt undo. + keybinds.terminal_suspend = "none" + keybinds.input_undo ??= unique(["ctrl+z", ...Config.Keybinds.shape.input_undo.parse(undefined).split(",")]).join( + ",", + ) + } + acc.result.keybinds = Config.Keybinds.parse(keybinds) const deps: Promise[] = [] if (acc.result.plugin?.length) { diff --git a/packages/opencode/test/config/tui.test.ts b/packages/opencode/test/config/tui.test.ts index a8d98b66cdec..b761d59ea4e6 100644 --- a/packages/opencode/test/config/tui.test.ts +++ b/packages/opencode/test/config/tui.test.ts @@ -9,6 +9,7 @@ import { Global } from "../../src/global" import { Filesystem } from "../../src/util/filesystem" const managedConfigDir = process.env.OPENCODE_TEST_MANAGED_CONFIG_DIR! +const wintest = process.platform === "win32" ? test : test.skip beforeEach(async () => { await Config.invalidate(true) @@ -441,6 +442,53 @@ test("merges keybind overrides across precedence layers", async () => { }) }) +wintest("defaults Ctrl+Z to input undo on Windows", async () => { + await using tmp = await tmpdir() + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await TuiConfig.get() + expect(config.keybinds?.terminal_suspend).toBe("none") + expect(config.keybinds?.input_undo).toBe("ctrl+z,ctrl+-,super+z") + }, + }) +}) + +wintest("keeps explicit input undo overrides on Windows", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write(path.join(dir, "tui.json"), JSON.stringify({ keybinds: { input_undo: "ctrl+y" } })) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await TuiConfig.get() + expect(config.keybinds?.terminal_suspend).toBe("none") + expect(config.keybinds?.input_undo).toBe("ctrl+y") + }, + }) +}) + +wintest("ignores terminal suspend bindings on Windows", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write(path.join(dir, "tui.json"), JSON.stringify({ keybinds: { terminal_suspend: "alt+z" } })) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await TuiConfig.get() + expect(config.keybinds?.terminal_suspend).toBe("none") + expect(config.keybinds?.input_undo).toBe("ctrl+z,ctrl+-,super+z") + }, + }) +}) + test("OPENCODE_TUI_CONFIG provides settings when no project config exists", async () => { await using tmp = await tmpdir({ init: async (dir) => { From d6f0ae5c059a0686164eb824e114487b46fcc799 Mon Sep 17 00:00:00 2001 From: opencode Date: Mon, 6 Apr 2026 03:44:46 +0000 Subject: [PATCH 094/616] release: v1.3.16 --- bun.lock | 32 +++++++++++++------------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop-electron/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++----- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/util/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index a7505304a8b1..375176e622d5 100644 --- a/bun.lock +++ b/bun.lock @@ -25,7 +25,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -79,7 +79,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -113,7 +113,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -140,7 +140,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -164,7 +164,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -188,7 +188,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -221,7 +221,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -253,7 +253,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -282,7 +282,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -298,7 +298,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.3.15", + "version": "1.3.16", "bin": { "opencode": "./bin/opencode", }, @@ -462,7 +462,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -496,7 +496,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "cross-spawn": "catalog:", }, @@ -511,7 +511,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -546,7 +546,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -594,7 +594,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "zod": "catalog:", }, @@ -605,7 +605,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 3d71ec1e7fc0..f9e3effb50a6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.3.15", + "version": "1.3.16", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 6b52b23dc23d..7b6be5eca00f 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.3.15", + "version": "1.3.16", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 3a1fa330f614..ac9bdc6235d4 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.3.15", + "version": "1.3.16", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index b630e41abc11..61b3dddd655a 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.3.15", + "version": "1.3.16", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index ff81ab887395..b7a5a6febe24 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.3.15", + "version": "1.3.16", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 64af9910e2b0..41ebacff8ce9 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.3.15", + "version": "1.3.16", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index eb0c8034cdb0..ca6b2dbeda93 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.3.15", + "version": "1.3.16", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index aee89a15047b..13fefec83559 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.3.15", + "version": "1.3.16", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index dc4a944ba101..6f1b0a7fe8f8 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.3.15" +version = "1.3.16" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/anomalyco/opencode" @@ -11,26 +11,26 @@ name = "OpenCode" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.15/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.16/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.15/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.16/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.15/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.16/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.15/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.16/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.15/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.16/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 637adefec200..c853251ccdad 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.3.15", + "version": "1.3.16", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index e92dd991cc25..3ec9d8e03369 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.3.15", + "version": "1.3.16", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index f4bd4e401c77..b05d822c654f 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.3.15", + "version": "1.3.16", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 04fb5194ad72..ff8255120b01 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.3.15", + "version": "1.3.16", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/slack/package.json b/packages/slack/package.json index 3de4f3b61507..8b8587180982 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.3.15", + "version": "1.3.16", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 16268d9c746c..f769113006f6 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.3.15", + "version": "1.3.16", "type": "module", "license": "MIT", "exports": { diff --git a/packages/util/package.json b/packages/util/package.json index 7b3cf2159008..a84f4c713dc0 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.3.15", + "version": "1.3.16", "private": true, "type": "module", "license": "MIT", diff --git a/packages/web/package.json b/packages/web/package.json index 24bb2354c3b0..7d898447d549 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.3.15", + "version": "1.3.16", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 36e9ec554015..564669fc26d8 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.3.15", + "version": "1.3.16", "publisher": "sst-dev", "repository": { "type": "git", From 533773ba0532b727907a1239c7699f2939338778 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 5 Apr 2026 23:51:34 -0400 Subject: [PATCH 095/616] zen: remove header check --- packages/console/app/src/routes/zen/util/rateLimiter.ts | 5 ++--- packages/console/core/src/subscription.ts | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/console/app/src/routes/zen/util/rateLimiter.ts b/packages/console/app/src/routes/zen/util/rateLimiter.ts index 042ec51f2c88..160633981047 100644 --- a/packages/console/app/src/routes/zen/util/rateLimiter.ts +++ b/packages/console/app/src/routes/zen/util/rateLimiter.ts @@ -17,9 +17,8 @@ export function createRateLimiter( const dict = i18n(localeFromRequest(request)) const limits = Subscription.getFreeLimits() - const headerExists = request.headers.has(limits.checkHeader) - const dailyLimit = !headerExists ? limits.fallbackValue : (rateLimit ?? limits.dailyRequests) - const isDefaultModel = headerExists && !rateLimit + const dailyLimit = rateLimit ?? limits.dailyRequests + const isDefaultModel = !rateLimit const ip = !rawIp.length ? "unknown" : rawIp const now = Date.now() diff --git a/packages/console/core/src/subscription.ts b/packages/console/core/src/subscription.ts index 9d6c3ce2b586..bee58184587a 100644 --- a/packages/console/core/src/subscription.ts +++ b/packages/console/core/src/subscription.ts @@ -9,8 +9,6 @@ export namespace Subscription { free: z.object({ promoTokens: z.number().int(), dailyRequests: z.number().int(), - checkHeader: z.string(), - fallbackValue: z.number().int(), }), lite: z.object({ rollingLimit: z.number().int(), From 632238fb92823937b8f35e3efa02d8e86244c9d3 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 6 Apr 2026 00:32:55 -0400 Subject: [PATCH 096/616] zen: normalize ipv6 --- packages/console/app/src/routes/zen/util/handler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index db5977bc16fe..3e191918e569 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -90,7 +90,8 @@ export async function handler( const body = await input.request.json() const model = opts.parseModel(url, body) const isStream = opts.parseIsStream(url, body) - const ip = input.request.headers.get("x-real-ip") ?? "" + const rawIp = input.request.headers.get("x-real-ip") ?? "" + const ip = rawIp.includes(":") ? rawIp.split(":").slice(0, 4).join(":") : rawIp const sessionId = input.request.headers.get("x-opencode-session") ?? "" const requestId = input.request.headers.get("x-opencode-request") ?? "" const projectId = input.request.headers.get("x-opencode-project") ?? "" From adcb2a5b1d8ed55cc582cd1c32f16ef45c31f9e4 Mon Sep 17 00:00:00 2001 From: MC Date: Mon, 6 Apr 2026 01:26:04 -0400 Subject: [PATCH 097/616] fix: show clear error when Cloudflare provider env vars are missing (#20399) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Aiden Cline --- packages/opencode/src/auth/index.ts | 1 + .../cli/cmd/tui/component/dialog-provider.tsx | 12 +++- packages/opencode/src/plugin/cloudflare.ts | 67 +++++++++++++++++++ packages/opencode/src/plugin/index.ts | 10 ++- packages/opencode/src/provider/provider.ts | 45 +++++++++++-- packages/sdk/js/src/v2/gen/types.gen.ts | 3 + packages/sdk/openapi.json | 9 +++ 7 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 packages/opencode/src/plugin/cloudflare.ts diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts index b6d340cc8ddf..2a9fb6c19e93 100644 --- a/packages/opencode/src/auth/index.ts +++ b/packages/opencode/src/auth/index.ts @@ -24,6 +24,7 @@ export namespace Auth { export class Api extends Schema.Class("ApiAuth")({ type: Schema.Literal("api"), key: Schema.String, + metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)), }) {} export class WellKnown extends Schema.Class("WellKnownAuth")({ diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx index 8add73dd6e45..cb7abb8227ed 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx @@ -129,7 +129,15 @@ export function createDialogProviderOptions() { } } if (method.type === "api") { - return dialog.replace(() => ) + let metadata: Record | undefined + if (method.prompts?.length) { + const value = await PromptsMethod({ dialog, prompts: method.prompts }) + if (!value) return + metadata = value + } + return dialog.replace(() => ( + + )) } }, } @@ -249,6 +257,7 @@ function CodeMethod(props: CodeMethodProps) { interface ApiMethodProps { providerID: string title: string + metadata?: Record } function ApiMethod(props: ApiMethodProps) { const dialog = useDialog() @@ -293,6 +302,7 @@ function ApiMethod(props: ApiMethodProps) { auth: { type: "api", key: value, + ...(props.metadata ? { metadata: props.metadata } : {}), }, }) await sdk.client.instance.dispose() diff --git a/packages/opencode/src/plugin/cloudflare.ts b/packages/opencode/src/plugin/cloudflare.ts new file mode 100644 index 000000000000..e20a488a3647 --- /dev/null +++ b/packages/opencode/src/plugin/cloudflare.ts @@ -0,0 +1,67 @@ +import type { Hooks, PluginInput } from "@opencode-ai/plugin" + +export async function CloudflareWorkersAuthPlugin(_input: PluginInput): Promise { + const prompts = [ + ...(!process.env.CLOUDFLARE_ACCOUNT_ID + ? [ + { + type: "text" as const, + key: "accountId", + message: "Enter your Cloudflare Account ID", + placeholder: "e.g. 1234567890abcdef1234567890abcdef", + }, + ] + : []), + ] + + return { + auth: { + provider: "cloudflare-workers-ai", + methods: [ + { + type: "api", + label: "API key", + prompts, + }, + ], + }, + } +} + +export async function CloudflareAIGatewayAuthPlugin(_input: PluginInput): Promise { + const prompts = [ + ...(!process.env.CLOUDFLARE_ACCOUNT_ID + ? [ + { + type: "text" as const, + key: "accountId", + message: "Enter your Cloudflare Account ID", + placeholder: "e.g. 1234567890abcdef1234567890abcdef", + }, + ] + : []), + ...(!process.env.CLOUDFLARE_GATEWAY_ID + ? [ + { + type: "text" as const, + key: "gatewayId", + message: "Enter your Cloudflare AI Gateway ID", + placeholder: "e.g. my-gateway", + }, + ] + : []), + ] + + return { + auth: { + provider: "cloudflare-ai-gateway", + methods: [ + { + type: "api", + label: "Gateway API token", + prompts, + }, + ], + }, + } +} diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index fb60fa096e88..df69c8eba708 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -10,6 +10,7 @@ import { NamedError } from "@opencode-ai/util/error" import { CopilotAuthPlugin } from "./github-copilot/copilot" import { gitlabAuthPlugin as GitlabAuthPlugin } from "opencode-gitlab-auth" import { PoeAuthPlugin } from "opencode-poe-auth" +import { CloudflareAIGatewayAuthPlugin, CloudflareWorkersAuthPlugin } from "./cloudflare" import { Effect, Layer, ServiceMap, Stream } from "effect" import { InstanceState } from "@/effect/instance-state" import { makeRuntime } from "@/effect/run-service" @@ -46,7 +47,14 @@ export namespace Plugin { export class Service extends ServiceMap.Service()("@opencode/Plugin") {} // Built-in plugins that are directly imported (not installed from npm) - const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin, CopilotAuthPlugin, GitlabAuthPlugin, PoeAuthPlugin] + const INTERNAL_PLUGINS: PluginInstance[] = [ + CodexAuthPlugin, + CopilotAuthPlugin, + GitlabAuthPlugin, + PoeAuthPlugin, + CloudflareWorkersAuthPlugin, + CloudflareAIGatewayAuthPlugin, + ] function isServerPlugin(value: unknown): value is PluginInstance { return typeof value === "function" diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 924d13312a68..9ca49bf8f106 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -672,13 +672,26 @@ export namespace Provider { } }), "cloudflare-workers-ai": Effect.fnUntraced(function* (input: Info) { - const accountId = Env.get("CLOUDFLARE_ACCOUNT_ID") - if (!accountId) return { autoload: false } + // When baseURL is already configured (e.g. corporate config routing through a proxy/gateway), + // skip the account ID check because the URL is already fully specified. + if (input.options?.baseURL) return { autoload: false } + + const auth = yield* dep.auth(input.id) + const accountId = + Env.get("CLOUDFLARE_ACCOUNT_ID") || (auth?.type === "api" ? auth.metadata?.accountId : undefined) + if (!accountId) + return { + autoload: false, + async getModel() { + throw new Error( + "CLOUDFLARE_ACCOUNT_ID is missing. Set it with: export CLOUDFLARE_ACCOUNT_ID=", + ) + }, + } const apiKey = yield* Effect.gen(function* () { const envToken = Env.get("CLOUDFLARE_API_KEY") if (envToken) return envToken - const auth = yield* dep.auth(input.id) if (auth?.type === "api") return auth.key return undefined }) @@ -702,16 +715,34 @@ export namespace Provider { } }), "cloudflare-ai-gateway": Effect.fnUntraced(function* (input: Info) { - const accountId = Env.get("CLOUDFLARE_ACCOUNT_ID") - const gateway = Env.get("CLOUDFLARE_GATEWAY_ID") + // When baseURL is already configured (e.g. corporate config), skip the ID checks. + if (input.options?.baseURL) return { autoload: false } - if (!accountId || !gateway) return { autoload: false } + const auth = yield* dep.auth(input.id) + const accountId = + Env.get("CLOUDFLARE_ACCOUNT_ID") || (auth?.type === "api" ? auth.metadata?.accountId : undefined) + const gateway = + Env.get("CLOUDFLARE_GATEWAY_ID") || (auth?.type === "api" ? auth.metadata?.gatewayId : undefined) + + if (!accountId || !gateway) { + const missing = [ + !accountId ? "CLOUDFLARE_ACCOUNT_ID" : undefined, + !gateway ? "CLOUDFLARE_GATEWAY_ID" : undefined, + ].filter((x): x is string => Boolean(x)) + return { + autoload: false, + async getModel() { + throw new Error( + `${missing.join(" and ")} missing. Set with: ${missing.map((x) => `export ${x}=`).join(" && ")}`, + ) + }, + } + } // Get API token from env or auth - required for authenticated gateways const apiToken = yield* Effect.gen(function* () { const envToken = Env.get("CLOUDFLARE_API_TOKEN") || Env.get("CF_AIG_TOKEN") if (envToken) return envToken - const auth = yield* dep.auth(input.id) if (auth?.type === "api") return auth.key return undefined }) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 72e549e485ab..548ab8363e2c 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1639,6 +1639,9 @@ export type OAuth = { export type ApiAuth = { type: "api" key: string + metadata?: { + [key: string]: string + } } export type WellKnownAuth = { diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 1aa4010e7ade..e21c48e89a3a 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -11621,6 +11621,15 @@ }, "key": { "type": "string" + }, + "metadata": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } } }, "required": ["type", "key"] From 8032ddd092e0658d8b1afbbaeb0f119864dec156 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:04:50 +1000 Subject: [PATCH 098/616] fix(tui): revert kitty keyboard events workaround on windows (#20180) --- packages/opencode/src/cli/cmd/tui/app.tsx | 2 +- .../src/cli/cmd/tui/component/prompt/index.tsx | 16 +--------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 93a5da037f32..a80039dd2ef0 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -132,7 +132,7 @@ function rendererConfig(_config: TuiConfig.Info): CliRendererConfig { targetFps: 60, gatherStats: false, exitOnCtrlC: false, - useKittyKeyboard: { events: process.platform === "win32" }, + useKittyKeyboard: {}, autoFocus: false, openConsoleOnError: false, useMouse: mouseEnabled, diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 5123cea56754..087742a979d7 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -18,7 +18,7 @@ import { usePromptStash } from "./stash" import { DialogStash } from "../dialog-stash" import { type AutocompleteRef, Autocomplete } from "./autocomplete" import { useCommandDialog } from "../dialog-command" -import { useKeyboard, useRenderer, type JSX } from "@opentui/solid" +import { useRenderer, type JSX } from "@opentui/solid" import { Editor } from "@tui/util/editor" import { useExit } from "../../context/exit" import { Clipboard } from "../../util/clipboard" @@ -400,20 +400,6 @@ export function Prompt(props: PromptProps) { ] }) - // Windows Terminal 1.25+ handles Ctrl+V on keydown when kitty events are - // enabled, but still reports the kitty key-release event. Probe on release. - if (process.platform === "win32") { - useKeyboard( - (evt) => { - if (!input.focused) return - if (evt.name === "v" && evt.ctrl && evt.eventType === "release") { - command.trigger("prompt.paste") - } - }, - { release: true }, - ) - } - const ref: PromptRef = { get focused() { return input.focused From e8f59b274356e1d746c61ac693c1d2f8a48169c6 Mon Sep 17 00:00:00 2001 From: opencode Date: Mon, 6 Apr 2026 07:39:18 +0000 Subject: [PATCH 099/616] release: v1.3.17 --- bun.lock | 32 +++++++++++++------------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop-electron/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++----- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/util/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index 375176e622d5..fcb8c9e053cb 100644 --- a/bun.lock +++ b/bun.lock @@ -25,7 +25,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -79,7 +79,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -113,7 +113,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -140,7 +140,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -164,7 +164,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -188,7 +188,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -221,7 +221,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -253,7 +253,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -282,7 +282,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -298,7 +298,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.3.16", + "version": "1.3.17", "bin": { "opencode": "./bin/opencode", }, @@ -462,7 +462,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -496,7 +496,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "cross-spawn": "catalog:", }, @@ -511,7 +511,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -546,7 +546,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -594,7 +594,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "zod": "catalog:", }, @@ -605,7 +605,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index f9e3effb50a6..cb52544eab67 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.3.16", + "version": "1.3.17", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 7b6be5eca00f..1a01bf21b2ab 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.3.16", + "version": "1.3.17", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index ac9bdc6235d4..ae5185e11648 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.3.16", + "version": "1.3.17", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 61b3dddd655a..e05a516533b1 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.3.16", + "version": "1.3.17", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index b7a5a6febe24..63878f7c5551 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.3.16", + "version": "1.3.17", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 41ebacff8ce9..2e851d6cf070 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.3.16", + "version": "1.3.17", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index ca6b2dbeda93..d4a8244468c2 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.3.16", + "version": "1.3.17", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 13fefec83559..e046366dc950 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.3.16", + "version": "1.3.17", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 6f1b0a7fe8f8..2c8e827ba7b8 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.3.16" +version = "1.3.17" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/anomalyco/opencode" @@ -11,26 +11,26 @@ name = "OpenCode" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.16/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.17/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.16/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.17/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.16/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.17/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.16/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.17/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.16/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.3.17/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index c853251ccdad..ae7f1c48d07d 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.3.16", + "version": "1.3.17", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 3ec9d8e03369..c48e085c7c57 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.3.16", + "version": "1.3.17", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index b05d822c654f..5710476fd2bc 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.3.16", + "version": "1.3.17", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index ff8255120b01..03ff4b46b3e7 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.3.16", + "version": "1.3.17", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/slack/package.json b/packages/slack/package.json index 8b8587180982..dad6eac206b3 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.3.16", + "version": "1.3.17", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index f769113006f6..64520f707c0a 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.3.16", + "version": "1.3.17", "type": "module", "license": "MIT", "exports": { diff --git a/packages/util/package.json b/packages/util/package.json index a84f4c713dc0..0eab52cccc9f 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.3.16", + "version": "1.3.17", "private": true, "type": "module", "license": "MIT", diff --git a/packages/web/package.json b/packages/web/package.json index 7d898447d549..77f54ef67c43 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.3.16", + "version": "1.3.17", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 564669fc26d8..3d3f94a819bd 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.3.16", + "version": "1.3.17", "publisher": "sst-dev", "repository": { "type": "git", From 22770f0e64c61ce35c7a3f5038cf503115d5cadb Mon Sep 17 00:00:00 2001 From: Derek Barrera Date: Mon, 6 Apr 2026 09:50:36 -0400 Subject: [PATCH 100/616] fix(lsp): MEMORY LEAK: ensure typescript server uses native project config (#19953) --- packages/opencode/src/lsp/server.ts | 12 +++- packages/opencode/test/lsp/index.test.ts | 78 +++++++++++++++++++++ packages/web/src/content/docs/ecosystem.mdx | 2 + 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index aa9bc884a83a..7421ed5436ca 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -105,7 +105,17 @@ export namespace LSPServer { if (!tsserver) return const bin = await Npm.which("typescript-language-server") if (!bin) return - const proc = spawn(bin, ["--stdio"], { + + const args = ["--stdio", "--tsserver-log-verbosity", "off", "--tsserver-path", tsserver] + + if ( + !(await pathExists(path.join(root, "tsconfig.json"))) && + !(await pathExists(path.join(root, "jsconfig.json"))) + ) { + args.push("--ignore-node-modules") + } + + const proc = spawn(bin, args, { cwd: root, env: { ...process.env, diff --git a/packages/opencode/test/lsp/index.test.ts b/packages/opencode/test/lsp/index.test.ts index 7e514e39b159..ceadfe6518a6 100644 --- a/packages/opencode/test/lsp/index.test.ts +++ b/packages/opencode/test/lsp/index.test.ts @@ -1,6 +1,8 @@ import { describe, expect, spyOn, test } from "bun:test" import path from "path" +import fs from "fs/promises" import * as Lsp from "../../src/lsp/index" +import * as launch from "../../src/lsp/launch" import { LSPServer } from "../../src/lsp/server" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" @@ -52,4 +54,80 @@ describe("lsp.spawn", () => { await Instance.disposeAll() } }) + + test("spawns builtin Typescript LSP with correct arguments", async () => { + await using tmp = await tmpdir() + + // Create dummy tsserver to satisfy Module.resolve + const tsdk = path.join(tmp.path, "node_modules", "typescript", "lib") + await fs.mkdir(tsdk, { recursive: true }) + await fs.writeFile(path.join(tsdk, "tsserver.js"), "") + + const spawnSpy = spyOn(launch, "spawn").mockImplementation( + () => + ({ + stdin: {}, + stdout: {}, + stderr: {}, + on: () => {}, + kill: () => {}, + }) as any, + ) + + try { + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await LSPServer.Typescript.spawn(tmp.path) + }, + }) + + expect(spawnSpy).toHaveBeenCalled() + const args = spawnSpy.mock.calls[0][1] as string[] + + expect(args).toContain("--tsserver-path") + expect(args).toContain("--tsserver-log-verbosity") + expect(args).toContain("off") + } finally { + spawnSpy.mockRestore() + } + }) + + test("spawns builtin Typescript LSP with --ignore-node-modules if no config is found", async () => { + await using tmp = await tmpdir() + + // Create dummy tsserver to satisfy Module.resolve + const tsdk = path.join(tmp.path, "node_modules", "typescript", "lib") + await fs.mkdir(tsdk, { recursive: true }) + await fs.writeFile(path.join(tsdk, "tsserver.js"), "") + + // NO tsconfig.json or jsconfig.json created here + + const spawnSpy = spyOn(launch, "spawn").mockImplementation( + () => + ({ + stdin: {}, + stdout: {}, + stderr: {}, + on: () => {}, + kill: () => {}, + }) as any, + ) + + try { + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await LSPServer.Typescript.spawn(tmp.path) + }, + }) + + expect(spawnSpy).toHaveBeenCalled() + const args = spawnSpy.mock.calls[0][1] as string[] + + expect(args).toContain("--ignore-node-modules") + } finally { + spawnSpy.mockRestore() + } + }) }) diff --git a/packages/web/src/content/docs/ecosystem.mdx b/packages/web/src/content/docs/ecosystem.mdx index 30b53eeca728..055daf14193d 100644 --- a/packages/web/src/content/docs/ecosystem.mdx +++ b/packages/web/src/content/docs/ecosystem.mdx @@ -32,6 +32,7 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instructions for non-interactive shell commands - prevents hangs from TTY-dependent operations | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Track OpenCode usage with Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Clean up markdown tables produced by LLMs | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x faster code editing with Morph Fast Apply API and lazy edit markers | | [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply editing, WarpGrep codebase search, and context compaction via Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Background agents, pre-built LSP/AST/MCP tools, curated agents, Claude Code compatible | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop notifications and sound alerts for OpenCode sessions | @@ -42,6 +43,7 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw | [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interactive plan review with visual annotation and private/offline sharing | | [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Extend opencode /commands into a powerful orchestration system with granular flow control | | [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Schedule recurring jobs using launchd (Mac) or systemd (Linux) with cron syntax | +| [opencode-conductor](https://github.com/derekbar90/opencode-conductor) | Protocol-Driven Workflow: Automation of the Context -> Spec -> Plan -> Implement lifecycle. | | [micode](https://github.com/vtemian/micode) | Structured Brainstorm → Plan → Implement workflow with session continuity | | [octto](https://github.com/vtemian/octto) | Interactive browser UI for AI brainstorming with multi-question forms | | [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code-style background agents with async delegation and context persistence | From 5b0ca277d07ddfac425fbbf82793967a23a689d9 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 6 Apr 2026 13:51:36 +0000 Subject: [PATCH 101/616] chore: generate --- packages/opencode/test/lsp/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/test/lsp/index.test.ts b/packages/opencode/test/lsp/index.test.ts index ceadfe6518a6..cfab72d83471 100644 --- a/packages/opencode/test/lsp/index.test.ts +++ b/packages/opencode/test/lsp/index.test.ts @@ -57,7 +57,7 @@ describe("lsp.spawn", () => { test("spawns builtin Typescript LSP with correct arguments", async () => { await using tmp = await tmpdir() - + // Create dummy tsserver to satisfy Module.resolve const tsdk = path.join(tmp.path, "node_modules", "typescript", "lib") await fs.mkdir(tsdk, { recursive: true }) From 766e48e77cee31b0d35941143ba7715d26edfe36 Mon Sep 17 00:00:00 2001 From: MC Date: Mon, 6 Apr 2026 11:50:24 -0400 Subject: [PATCH 102/616] docs: update Cloudflare provider setup to reflect /connect prompt flow (#20589) --- packages/web/src/content/docs/providers.mdx | 69 +++++++++++++-------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index b14c8ab10a8e..bd7e10f92894 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -490,37 +490,42 @@ If you encounter "I'm sorry, but I cannot assist with that request" errors, try Cloudflare AI Gateway lets you access models from OpenAI, Anthropic, Workers AI, and more through a unified endpoint. With [Unified Billing](https://developers.cloudflare.com/ai-gateway/features/unified-billing/) you don't need separate API keys for each provider. -1. Head over to the [Cloudflare dashboard](https://dash.cloudflare.com/), navigate to **AI** > **AI Gateway**, and create a new gateway. +1. Head over to the [Cloudflare dashboard](https://dash.cloudflare.com/), navigate to **AI** > **AI Gateway**, and create a new gateway. Note your **Account ID** and **Gateway ID**. -2. Set your Account ID and Gateway ID as environment variables. +2. Run the `/connect` command and search for **Cloudflare AI Gateway**. - ```bash title="~/.bash_profile" - export CLOUDFLARE_ACCOUNT_ID=your-32-character-account-id - export CLOUDFLARE_GATEWAY_ID=your-gateway-id + ```txt + /connect ``` -3. Run the `/connect` command and search for **Cloudflare AI Gateway**. +3. Enter your **Account ID** when prompted. ```txt - /connect + ┌ Enter your Cloudflare Account ID + │ + │ + └ enter ``` -4. Enter your Cloudflare API token. +4. Enter your **Gateway ID** when prompted. ```txt - ┌ API key + ┌ Enter your Cloudflare AI Gateway ID │ │ └ enter ``` - Or set it as an environment variable. +5. Enter your **Cloudflare API token**. - ```bash title="~/.bash_profile" - export CLOUDFLARE_API_TOKEN=your-api-token + ```txt + ┌ Gateway API token + │ + │ + └ enter ``` -5. Run the `/models` command to select a model. +6. Run the `/models` command to select a model. ```txt /models @@ -542,39 +547,44 @@ Cloudflare AI Gateway lets you access models from OpenAI, Anthropic, Workers AI, } ``` + Alternatively, you can set environment variables instead of using `/connect`. + + ```bash title="~/.bash_profile" + export CLOUDFLARE_ACCOUNT_ID=your-32-character-account-id + export CLOUDFLARE_GATEWAY_ID=your-gateway-id + export CLOUDFLARE_API_TOKEN=your-api-token + ``` + --- ### Cloudflare Workers AI Cloudflare Workers AI lets you run AI models on Cloudflare's global network directly via REST API, with no separate provider accounts needed for supported models. -1. Head over to the [Cloudflare dashboard](https://dash.cloudflare.com/), navigate to **Workers AI**, and select **Use REST API** to get your Account ID and create an API token. - -2. Set your Account ID as an environment variable. +1. Head over to the [Cloudflare dashboard](https://dash.cloudflare.com/), navigate to **Workers AI**, and select **Use REST API** to get your **Account ID** and create an API token. - ```bash title="~/.bash_profile" - export CLOUDFLARE_ACCOUNT_ID=your-32-character-account-id - ``` - -3. Run the `/connect` command and search for **Cloudflare Workers AI**. +2. Run the `/connect` command and search for **Cloudflare Workers AI**. ```txt /connect ``` -4. Enter your Cloudflare API token. +3. Enter your **Account ID** when prompted. ```txt - ┌ API key + ┌ Enter your Cloudflare Account ID │ │ └ enter ``` - Or set it as an environment variable. +4. Enter your **Cloudflare API key**. - ```bash title="~/.bash_profile" - export CLOUDFLARE_API_KEY=your-api-token + ```txt + ┌ API key + │ + │ + └ enter ``` 5. Run the `/models` command to select a model. @@ -583,6 +593,13 @@ Cloudflare Workers AI lets you run AI models on Cloudflare's global network dire /models ``` + Alternatively, you can set environment variables instead of using `/connect`. + + ```bash title="~/.bash_profile" + export CLOUDFLARE_ACCOUNT_ID=your-32-character-account-id + export CLOUDFLARE_API_KEY=your-api-token + ``` + --- ### Cortecs From 8f0285fd8abababb17a9df4eea02e632e2d250a7 Mon Sep 17 00:00:00 2001 From: Dax Date: Mon, 6 Apr 2026 12:17:29 -0400 Subject: [PATCH 103/616] refactor: replace Bun.serve with Node http.createServer in OAuth handlers (#18327) Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com> --- packages/opencode/src/mcp/oauth-callback.ts | 161 ++++++++++---------- packages/opencode/src/plugin/codex.ts | 117 +++++++------- 2 files changed, 143 insertions(+), 135 deletions(-) diff --git a/packages/opencode/src/mcp/oauth-callback.ts b/packages/opencode/src/mcp/oauth-callback.ts index 3a1ca5404424..dd1d886fc1b4 100644 --- a/packages/opencode/src/mcp/oauth-callback.ts +++ b/packages/opencode/src/mcp/oauth-callback.ts @@ -1,4 +1,5 @@ import { createConnection } from "net" +import { createServer } from "http" import { Log } from "../util/log" import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH } from "./oauth-provider" @@ -52,7 +53,7 @@ interface PendingAuth { } export namespace McpOAuthCallback { - let server: ReturnType | undefined + let server: ReturnType | undefined const pendingAuths = new Map() // Reverse index: mcpName → oauthState, so cancelPending(mcpName) can // find the right entry in pendingAuths (which is keyed by oauthState). @@ -60,97 +61,97 @@ export namespace McpOAuthCallback { const CALLBACK_TIMEOUT_MS = 5 * 60 * 1000 // 5 minutes - export async function ensureRunning(): Promise { - if (server) return + function cleanupStateIndex(oauthState: string) { + for (const [name, state] of mcpNameToState) { + if (state === oauthState) { + mcpNameToState.delete(name) + break + } + } + } - const running = await isPortInUse() - if (running) { - log.info("oauth callback server already running on another instance", { port: OAUTH_CALLBACK_PORT }) + function handleRequest(req: import("http").IncomingMessage, res: import("http").ServerResponse) { + const url = new URL(req.url || "/", `http://localhost:${OAUTH_CALLBACK_PORT}`) + + if (url.pathname !== OAUTH_CALLBACK_PATH) { + res.writeHead(404) + res.end("Not found") return } - server = Bun.serve({ - port: OAUTH_CALLBACK_PORT, - fetch(req) { - const url = new URL(req.url) + const code = url.searchParams.get("code") + const state = url.searchParams.get("state") + const error = url.searchParams.get("error") + const errorDescription = url.searchParams.get("error_description") - if (url.pathname !== OAUTH_CALLBACK_PATH) { - return new Response("Not found", { status: 404 }) - } + log.info("received oauth callback", { hasCode: !!code, state, error }) - const code = url.searchParams.get("code") - const state = url.searchParams.get("state") - const error = url.searchParams.get("error") - const errorDescription = url.searchParams.get("error_description") - - log.info("received oauth callback", { hasCode: !!code, state, error }) - - // Enforce state parameter presence - if (!state) { - const errorMsg = "Missing required state parameter - potential CSRF attack" - log.error("oauth callback missing state parameter", { url: url.toString() }) - return new Response(HTML_ERROR(errorMsg), { - status: 400, - headers: { "Content-Type": "text/html" }, - }) - } + // Enforce state parameter presence + if (!state) { + const errorMsg = "Missing required state parameter - potential CSRF attack" + log.error("oauth callback missing state parameter", { url: url.toString() }) + res.writeHead(400, { "Content-Type": "text/html" }) + res.end(HTML_ERROR(errorMsg)) + return + } - if (error) { - const errorMsg = errorDescription || error - if (pendingAuths.has(state)) { - const pending = pendingAuths.get(state)! - clearTimeout(pending.timeout) - pendingAuths.delete(state) - for (const [name, s] of mcpNameToState) { - if (s === state) { - mcpNameToState.delete(name) - break - } - } - pending.reject(new Error(errorMsg)) - } - return new Response(HTML_ERROR(errorMsg), { - headers: { "Content-Type": "text/html" }, - }) - } + if (error) { + const errorMsg = errorDescription || error + if (pendingAuths.has(state)) { + const pending = pendingAuths.get(state)! + clearTimeout(pending.timeout) + pendingAuths.delete(state) + cleanupStateIndex(state) + pending.reject(new Error(errorMsg)) + } + res.writeHead(200, { "Content-Type": "text/html" }) + res.end(HTML_ERROR(errorMsg)) + return + } - if (!code) { - return new Response(HTML_ERROR("No authorization code provided"), { - status: 400, - headers: { "Content-Type": "text/html" }, - }) - } + if (!code) { + res.writeHead(400, { "Content-Type": "text/html" }) + res.end(HTML_ERROR("No authorization code provided")) + return + } - // Validate state parameter - if (!pendingAuths.has(state)) { - const errorMsg = "Invalid or expired state parameter - potential CSRF attack" - log.error("oauth callback with invalid state", { state, pendingStates: Array.from(pendingAuths.keys()) }) - return new Response(HTML_ERROR(errorMsg), { - status: 400, - headers: { "Content-Type": "text/html" }, - }) - } + // Validate state parameter + if (!pendingAuths.has(state)) { + const errorMsg = "Invalid or expired state parameter - potential CSRF attack" + log.error("oauth callback with invalid state", { state, pendingStates: Array.from(pendingAuths.keys()) }) + res.writeHead(400, { "Content-Type": "text/html" }) + res.end(HTML_ERROR(errorMsg)) + return + } - const pending = pendingAuths.get(state)! + const pending = pendingAuths.get(state)! - clearTimeout(pending.timeout) - pendingAuths.delete(state) - // Clean up reverse index - for (const [name, s] of mcpNameToState) { - if (s === state) { - mcpNameToState.delete(name) - break - } - } - pending.resolve(code) + clearTimeout(pending.timeout) + pendingAuths.delete(state) + cleanupStateIndex(state) + pending.resolve(code) - return new Response(HTML_SUCCESS, { - headers: { "Content-Type": "text/html" }, - }) - }, - }) + res.writeHead(200, { "Content-Type": "text/html" }) + res.end(HTML_SUCCESS) + } + + export async function ensureRunning(): Promise { + if (server) return + + const running = await isPortInUse() + if (running) { + log.info("oauth callback server already running on another instance", { port: OAUTH_CALLBACK_PORT }) + return + } - log.info("oauth callback server started", { port: OAUTH_CALLBACK_PORT }) + server = createServer(handleRequest) + await new Promise((resolve, reject) => { + server!.listen(OAUTH_CALLBACK_PORT, () => { + log.info("oauth callback server started", { port: OAUTH_CALLBACK_PORT }) + resolve() + }) + server!.on("error", reject) + }) } export function waitForCallback(oauthState: string, mcpName?: string): Promise { @@ -196,7 +197,7 @@ export namespace McpOAuthCallback { export async function stop(): Promise { if (server) { - server.stop() + await new Promise((resolve) => server!.close(() => resolve())) server = undefined log.info("oauth callback server stopped") } diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index ee42b9517198..77c00eb4f06e 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -6,6 +6,7 @@ import os from "os" import { ProviderTransform } from "@/provider/transform" import { ModelID, ProviderID } from "@/provider/schema" import { setTimeout as sleep } from "node:timers/promises" +import { createServer } from "http" const log = Log.create({ service: "plugin.codex" }) @@ -241,7 +242,7 @@ interface PendingOAuth { reject: (error: Error) => void } -let oauthServer: ReturnType | undefined +let oauthServer: ReturnType | undefined let pendingOAuth: PendingOAuth | undefined async function startOAuthServer(): Promise<{ port: number; redirectUri: string }> { @@ -249,77 +250,83 @@ async function startOAuthServer(): Promise<{ port: number; redirectUri: string } return { port: OAUTH_PORT, redirectUri: `http://localhost:${OAUTH_PORT}/auth/callback` } } - oauthServer = Bun.serve({ - port: OAUTH_PORT, - fetch(req) { - const url = new URL(req.url) + oauthServer = createServer((req, res) => { + const url = new URL(req.url || "/", `http://localhost:${OAUTH_PORT}`) - if (url.pathname === "/auth/callback") { - const code = url.searchParams.get("code") - const state = url.searchParams.get("state") - const error = url.searchParams.get("error") - const errorDescription = url.searchParams.get("error_description") + if (url.pathname === "/auth/callback") { + const code = url.searchParams.get("code") + const state = url.searchParams.get("state") + const error = url.searchParams.get("error") + const errorDescription = url.searchParams.get("error_description") - if (error) { - const errorMsg = errorDescription || error - pendingOAuth?.reject(new Error(errorMsg)) - pendingOAuth = undefined - return new Response(HTML_ERROR(errorMsg), { - headers: { "Content-Type": "text/html" }, - }) - } - - if (!code) { - const errorMsg = "Missing authorization code" - pendingOAuth?.reject(new Error(errorMsg)) - pendingOAuth = undefined - return new Response(HTML_ERROR(errorMsg), { - status: 400, - headers: { "Content-Type": "text/html" }, - }) - } - - if (!pendingOAuth || state !== pendingOAuth.state) { - const errorMsg = "Invalid state - potential CSRF attack" - pendingOAuth?.reject(new Error(errorMsg)) - pendingOAuth = undefined - return new Response(HTML_ERROR(errorMsg), { - status: 400, - headers: { "Content-Type": "text/html" }, - }) - } - - const current = pendingOAuth + if (error) { + const errorMsg = errorDescription || error + pendingOAuth?.reject(new Error(errorMsg)) pendingOAuth = undefined + res.writeHead(200, { "Content-Type": "text/html" }) + res.end(HTML_ERROR(errorMsg)) + return + } - exchangeCodeForTokens(code, `http://localhost:${OAUTH_PORT}/auth/callback`, current.pkce) - .then((tokens) => current.resolve(tokens)) - .catch((err) => current.reject(err)) - - return new Response(HTML_SUCCESS, { - headers: { "Content-Type": "text/html" }, - }) + if (!code) { + const errorMsg = "Missing authorization code" + pendingOAuth?.reject(new Error(errorMsg)) + pendingOAuth = undefined + res.writeHead(400, { "Content-Type": "text/html" }) + res.end(HTML_ERROR(errorMsg)) + return } - if (url.pathname === "/cancel") { - pendingOAuth?.reject(new Error("Login cancelled")) + if (!pendingOAuth || state !== pendingOAuth.state) { + const errorMsg = "Invalid state - potential CSRF attack" + pendingOAuth?.reject(new Error(errorMsg)) pendingOAuth = undefined - return new Response("Login cancelled", { status: 200 }) + res.writeHead(400, { "Content-Type": "text/html" }) + res.end(HTML_ERROR(errorMsg)) + return } - return new Response("Not found", { status: 404 }) - }, + const current = pendingOAuth + pendingOAuth = undefined + + exchangeCodeForTokens(code, `http://localhost:${OAUTH_PORT}/auth/callback`, current.pkce) + .then((tokens) => current.resolve(tokens)) + .catch((err) => current.reject(err)) + + res.writeHead(200, { "Content-Type": "text/html" }) + res.end(HTML_SUCCESS) + return + } + + if (url.pathname === "/cancel") { + pendingOAuth?.reject(new Error("Login cancelled")) + pendingOAuth = undefined + res.writeHead(200) + res.end("Login cancelled") + return + } + + res.writeHead(404) + res.end("Not found") + }) + + await new Promise((resolve, reject) => { + oauthServer!.listen(OAUTH_PORT, () => { + log.info("codex oauth server started", { port: OAUTH_PORT }) + resolve() + }) + oauthServer!.on("error", reject) }) - log.info("codex oauth server started", { port: OAUTH_PORT }) return { port: OAUTH_PORT, redirectUri: `http://localhost:${OAUTH_PORT}/auth/callback` } } function stopOAuthServer() { if (oauthServer) { - oauthServer.stop() + oauthServer.close(() => { + log.info("codex oauth server stopped") + }) oauthServer = undefined - log.info("codex oauth server stopped") } } From 02774a362b98694c0257e039364c6ccfa18aa5e3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 6 Apr 2026 18:42:05 +0200 Subject: [PATCH 104/616] upgrade opentui to 0.1.97 (#21137) --- bun.lock | 28 ++++++++++++++-------------- packages/opencode/package.json | 4 ++-- packages/plugin/package.json | 8 ++++---- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/bun.lock b/bun.lock index fcb8c9e053cb..bdcd8c0b74fb 100644 --- a/bun.lock +++ b/bun.lock @@ -340,8 +340,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "2.3.3", - "@opentui/core": "0.1.96", - "@opentui/solid": "0.1.96", + "@opentui/core": "0.1.97", + "@opentui/solid": "0.1.97", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", @@ -468,16 +468,16 @@ "zod": "catalog:", }, "devDependencies": { - "@opentui/core": "0.1.96", - "@opentui/solid": "0.1.96", + "@opentui/core": "0.1.97", + "@opentui/solid": "0.1.97", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", "@typescript/native-preview": "catalog:", "typescript": "catalog:", }, "peerDependencies": { - "@opentui/core": ">=0.1.96", - "@opentui/solid": ">=0.1.96", + "@opentui/core": ">=0.1.97", + "@opentui/solid": ">=0.1.97", }, "optionalPeers": [ "@opentui/core", @@ -1548,21 +1548,21 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@opentui/core": ["@opentui/core@0.1.96", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.96", "@opentui/core-darwin-x64": "0.1.96", "@opentui/core-linux-arm64": "0.1.96", "@opentui/core-linux-x64": "0.1.96", "@opentui/core-win32-arm64": "0.1.96", "@opentui/core-win32-x64": "0.1.96", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-VBO5zRiGM6fhibG3AwTMpf0JgbYWG0sXP5AsSJAYw8tQ18OCPj+EDLXGZ1DFmMnJWEi+glKYjmqnIp4yRCqi+Q=="], + "@opentui/core": ["@opentui/core@0.1.97", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.97", "@opentui/core-darwin-x64": "0.1.97", "@opentui/core-linux-arm64": "0.1.97", "@opentui/core-linux-x64": "0.1.97", "@opentui/core-win32-arm64": "0.1.97", "@opentui/core-win32-x64": "0.1.97", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-2ENH0Dc4NUAeHeeQCQhF1lg68RuyntOUP68UvortvDqTz/hqLG0tIwF+DboCKtWi8Nmao4SAQEJ7lfmyQNEDOQ=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.96", "", { "os": "darwin", "cpu": "arm64" }, "sha512-909i75uhLmlUFCK3LK4iICaymiA7QaB45X9IDX94KaDyHL3Y1PgYTzoRZLJlqeOfOBjVfEjMAh/zA5XexWDMpA=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.97", "", { "os": "darwin", "cpu": "arm64" }, "sha512-t7oMGEfMPQsqLEx7/rPqv/UGJ+vqhe4RWHRRQRYcuHuLKssZ2S8P9mSS7MBPtDqGcxg4PosCrh5nHYeZ94EXUw=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.96", "", { "os": "darwin", "cpu": "x64" }, "sha512-qukQjjScKldZAfgY9qVMPv4ZA6Ko7oXjNBUcSMGDgUiOitH6INT1cJQVUnAIu14DY15yEl08MEQ8soLDaSAHcg=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.97", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZuPWAawlVat6ZHb8vaH/CVUeGwI0pI4vd+6zz1ZocZn95ZWJztfyhzNZOJrq1WjHmUROieJ7cOuYUZfvYNuLrg=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.96", "", { "os": "linux", "cpu": "arm64" }, "sha512-9ktmyS24nfSmlFPX0GMWEaEYSjtEPbRn59y4KBhHVhzPsl+YKlzstyHomTBu51IAPu6oL3+t3Lu4gU+k1gFOQQ=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.97", "", { "os": "linux", "cpu": "arm64" }, "sha512-QXxhz654vXgEu2wrFFFFnrSWbyk6/r6nXNnDTcMRWofdMZQLx87NhbcsErNmz9KmFdzoPiQSmlpYubLflKKzqQ=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.96", "", { "os": "linux", "cpu": "x64" }, "sha512-m2pVhIdtqFYO+QSMc2VZgSSCNxRGPL+U+aKYYbvJjPzqCnIkHB9eO0ePU4b3t+V7GaWCcCP3vDCy3g1J5/FreA=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.97", "", { "os": "linux", "cpu": "x64" }, "sha512-v3z0QWpRS3p8blE/A7pTu15hcFMtSndeiYhRxhrjp6zAhQ+UlruQs9DAG1ifSuVO1RJJ0pUKklFivdbu0pMzuw=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.96", "", { "os": "win32", "cpu": "arm64" }, "sha512-OybZ4jvX6H6RKYyGpZqzy3ZrwKaxaXKWwFsmG6pC2J+GRhf5oCIIEy3Y5573h7zy1cq3T9cb225KzBANq9j5BA=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.97", "", { "os": "win32", "cpu": "arm64" }, "sha512-o/m9mD1dvOCwkxOUUyoEILl+d6tzh/85foJc4uqjXYi71NNcwg8u+Eq3/gdHuSKnlT1pusCPKoS1IDuBvZE24A=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.96", "", { "os": "win32", "cpu": "x64" }, "sha512-3YKjg90j14I7dJ94yN0pAYcTf4ogCoohv6ptRdG96XUyzrYhQiDMP398vCIOMjaLBjtMtFmTxSf+W46zm96BCQ=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.97", "", { "os": "win32", "cpu": "x64" }, "sha512-Rwp7JOwrYm4wtzPHY2vv+2l91LXmKSI7CtbmWN1sSUGhBPtPGSvfwux3W5xaAZQa2KPEXicPjaKJZc+pob3YRg=="], - "@opentui/solid": ["@opentui/solid@0.1.96", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.96", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-NGiVvG1ylswMjF9fzvpSaWLcZKQsPw67KRkIZgsdf4ZIKUZEZ94NktabCA92ti4WVGXhPvyM3SIX5S2+HvnJFg=="], + "@opentui/solid": ["@opentui/solid@0.1.97", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.97", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-ma/uihG38F+6oLJVD8yR7z82FWmR8QhfesNV5SBXbN74riMCRyy6kyQ6SI4xs4ykt9BbZOjrKLq+Xt/0Pd0SJQ=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index c48e085c7c57..00ee1db40ccd 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -99,8 +99,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "2.3.3", - "@opentui/core": "0.1.96", - "@opentui/solid": "0.1.96", + "@opentui/core": "0.1.97", + "@opentui/solid": "0.1.97", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 5710476fd2bc..0569a0a7cc2e 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -21,8 +21,8 @@ "zod": "catalog:" }, "peerDependencies": { - "@opentui/core": ">=0.1.96", - "@opentui/solid": ">=0.1.96" + "@opentui/core": ">=0.1.97", + "@opentui/solid": ">=0.1.97" }, "peerDependenciesMeta": { "@opentui/core": { @@ -33,8 +33,8 @@ } }, "devDependencies": { - "@opentui/core": "0.1.96", - "@opentui/solid": "0.1.96", + "@opentui/core": "0.1.97", + "@opentui/solid": "0.1.97", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", "typescript": "catalog:", From 59b52d8d017995c849858f69c00f98a396893a73 Mon Sep 17 00:00:00 2001 From: Dax Date: Mon, 6 Apr 2026 13:24:55 -0400 Subject: [PATCH 105/616] refactor(server): replace Bun serve with Hono node adapters (#18335) Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com> Co-authored-by: Brendan Allan --- bun.lock | 39 +++++- package.json | 2 + packages/app/script/e2e-local.ts | 6 +- packages/opencode/package.json | 11 ++ packages/opencode/script/build-node.ts | 15 +++ packages/opencode/script/fix-node-pty.ts | 28 ++++ packages/opencode/src/cli/cmd/acp.ts | 2 +- packages/opencode/src/cli/cmd/serve.ts | 2 +- packages/opencode/src/cli/cmd/web.ts | 2 +- packages/opencode/src/plugin/index.ts | 3 +- packages/opencode/src/pty/index.ts | 30 ++--- packages/opencode/src/pty/pty.bun.ts | 26 ++++ packages/opencode/src/pty/pty.node.ts | 27 ++++ packages/opencode/src/pty/pty.ts | 25 ++++ packages/opencode/src/server/instance.ts | 7 +- packages/opencode/src/server/router.ts | 127 ++++++++++--------- packages/opencode/src/server/routes/pty.ts | 13 +- packages/opencode/src/server/server.ts | 141 +++++++++++++-------- 18 files changed, 359 insertions(+), 147 deletions(-) mode change 100644 => 100755 packages/opencode/script/build-node.ts create mode 100755 packages/opencode/script/fix-node-pty.ts create mode 100644 packages/opencode/src/pty/pty.bun.ts create mode 100644 packages/opencode/src/pty/pty.node.ts create mode 100644 packages/opencode/src/pty/pty.ts diff --git a/bun.lock b/bun.lock index bdcd8c0b74fb..595482953241 100644 --- a/bun.lock +++ b/bun.lock @@ -328,8 +328,13 @@ "@aws-sdk/credential-providers": "3.993.0", "@clack/prompts": "1.0.0-alpha.1", "@effect/platform-node": "catalog:", + "@gitlab/gitlab-ai-provider": "3.6.0", + "@gitlab/opencode-gitlab-auth": "1.3.3", + "@hono/node-server": "1.19.11", + "@hono/node-ws": "1.3.0", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", + "@lydell/node-pty": "1.2.0-beta.10", "@modelcontextprotocol/sdk": "1.27.1", "@npmcli/arborist": "9.4.0", "@octokit/graphql": "9.0.2", @@ -1176,6 +1181,10 @@ "@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="], + "@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.6.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-8LmcIQ86xkMtC7L4P1/QYVEC+yKMTRerfPeniaaQGalnzXKtX6iMHLjLPOL9Rxp55lOXi6ed0WrFuJzZx+fNRg=="], + + "@gitlab/opencode-gitlab-auth": ["@gitlab/opencode-gitlab-auth@1.3.3", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-FT+KsCmAJjtqWr1YAq0MywGgL9kaLQ4apmsoowAXrPqHtoYf2i/nY10/A+L06kNj22EATeEDRpbB1NWXMto/SA=="], + "@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="], "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="], @@ -1188,7 +1197,9 @@ "@hey-api/types": ["@hey-api/types@0.1.2", "", {}, "sha512-uNNtiVAWL7XNrV/tFXx7GLY9lwaaDazx1173cGW3+UEaw4RUPsHEmiB4DSpcjNxMIcrctfz2sGKLnVx5PBG2RA=="], - "@hono/node-server": ["@hono/node-server@1.19.12", "", { "peerDependencies": { "hono": "^4" } }, "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw=="], + "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], + + "@hono/node-ws": ["@hono/node-ws@1.3.0", "", { "dependencies": { "ws": "^8.17.0" }, "peerDependencies": { "@hono/node-server": "^1.19.2", "hono": "^4.6.0" } }, "sha512-ju25YbbvLuXdqBCmLZLqnNYu1nbHIQjoyUqA8ApZOeL1k4skuiTcw5SW77/5SUYo2Xi2NVBJoVlfQurnKEp03Q=="], "@hono/standard-validator": ["@hono/standard-validator@0.1.5", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w=="], @@ -1386,6 +1397,20 @@ "@lukeed/ms": ["@lukeed/ms@2.0.2", "", {}, "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA=="], + "@lydell/node-pty": ["@lydell/node-pty@1.2.0-beta.10", "", { "optionalDependencies": { "@lydell/node-pty-darwin-arm64": "1.2.0-beta.10", "@lydell/node-pty-darwin-x64": "1.2.0-beta.10", "@lydell/node-pty-linux-arm64": "1.2.0-beta.10", "@lydell/node-pty-linux-x64": "1.2.0-beta.10", "@lydell/node-pty-win32-arm64": "1.2.0-beta.10", "@lydell/node-pty-win32-x64": "1.2.0-beta.10" } }, "sha512-Fv+A3+MZVA8qhkBIZsM1E6dCdHNMyXXz22mAYiMWd03LlyK///F3OH6CKPX9mj4id7LUlxpr45yPzyBVy9aDPw=="], + + "@lydell/node-pty-darwin-arm64": ["@lydell/node-pty-darwin-arm64@1.2.0-beta.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C+eqDyRNHRYvx7RaHj6VVCx6nCpRBPuuxhTcc3JH3GuBMoxTsYeY4GkWH2XOktrgbAq1BG8e/Y8bu/wNQreCEw=="], + + "@lydell/node-pty-darwin-x64": ["@lydell/node-pty-darwin-x64@1.2.0-beta.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-aZoIK6HtJO5BiT4ELm683U4dyHtt8b7wNgq3NJqYAQwSXrcPv576Z8vY3BIulVxfcFkht/SPLKou9TtdFXdNpg=="], + + "@lydell/node-pty-linux-arm64": ["@lydell/node-pty-linux-arm64@1.2.0-beta.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-0cKX2iMyXFNBE4fGtGK6B7IkdXcDMZajyEDoGMOgQQs/DDtoI5tSPcBcqNY9VitVrsRQA8+gFt6eKYU9Ye/lUA=="], + + "@lydell/node-pty-linux-x64": ["@lydell/node-pty-linux-x64@1.2.0-beta.10", "", { "os": "linux", "cpu": "x64" }, "sha512-J9HnxvSzEeMH748+Ul1VrmCLWMo7iCVJy9EGijRR62+YO/Yk5GaCydUTZ+KzlH0/X5aTrgt5cfiof4vx45tRRg=="], + + "@lydell/node-pty-win32-arm64": ["@lydell/node-pty-win32-arm64@1.2.0-beta.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-PlDJpJX/pnKyy6OmADKzhf+INZDDnzTBGaI0LT4laVNc6NblZNqUSkCMjLFWbeakeuQp0VG37M49WQSN9FDfeA=="], + + "@lydell/node-pty-win32-x64": ["@lydell/node-pty-win32-x64@1.2.0-beta.10", "", { "os": "win32", "cpu": "x64" }, "sha512-ExFgWrzyldNAMi45U9PLIOu+g/RatP+f0c/dZxaooifME6yLW32BoHveH26/TtoAjZyJrc2iL0u48pgnR1fzmg=="], + "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="], "@malept/flatpak-bundler": ["@malept/flatpak-bundler@0.4.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "tmp-promise": "^3.0.2" } }, "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q=="], @@ -5262,10 +5287,18 @@ "@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="], + "@gitlab/gitlab-ai-provider/openai": ["openai@6.33.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-xAYN1W3YsDXJWA5F277135YfkEk6H7D3D6vWwRhJ3OEkzRgcyK8z/P5P9Gyi/wB4N8kK9kM5ZjprfvyHagKmpw=="], + + "@gitlab/gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "@gitlab/opencode-gitlab-auth/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + "@hey-api/openapi-ts/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], "@hey-api/openapi-ts/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "@hono/node-ws/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + "@hono/zod-validator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@jimp/core/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], @@ -5322,6 +5355,8 @@ "@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + "@modelcontextprotocol/sdk/@hono/node-server": ["@hono/node-server@1.19.12", "", { "peerDependencies": { "hono": "^4" } }, "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw=="], + "@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], "@modelcontextprotocol/sdk/hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="], @@ -6164,6 +6199,8 @@ "@expressive-code/plugin-shiki/shiki/@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="], + "@gitlab/opencode-gitlab-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], + "@jsx-email/cli/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], "@jsx-email/cli/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], diff --git a/package.json b/package.json index fc73a94d264d..e4a2b5844f55 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev", "dev:storybook": "bun --cwd packages/storybook storybook", "typecheck": "bun turbo typecheck", + "postinstall": "bun run --cwd packages/opencode fix-node-pty", "prepare": "husky", "random": "echo 'Random script'", "hello": "echo 'Hello World!'", @@ -103,6 +104,7 @@ }, "trustedDependencies": [ "esbuild", + "node-pty", "protobufjs", "tree-sitter", "tree-sitter-bash", diff --git a/packages/app/script/e2e-local.ts b/packages/app/script/e2e-local.ts index 70442d0d76f3..4f0f795a3a89 100644 --- a/packages/app/script/e2e-local.ts +++ b/packages/app/script/e2e-local.ts @@ -87,7 +87,7 @@ const runnerEnv = { let seed: ReturnType | undefined let runner: ReturnType | undefined -let server: { stop: () => Promise | void } | undefined +let server: { stop: (close?: boolean) => Promise | void } | undefined let inst: { Instance: { disposeAll: () => Promise | void } } | undefined let cleaned = false @@ -100,7 +100,7 @@ const cleanup = async () => { const jobs = [ inst?.Instance.disposeAll(), - server?.stop(), + typeof server?.stop === "function" ? server.stop() : undefined, keepSandbox ? undefined : fs.rm(sandbox, { recursive: true, force: true }), ].filter(Boolean) await Promise.allSettled(jobs) @@ -158,7 +158,7 @@ try { const servermod = await import("../../opencode/src/server/server") inst = await import("../../opencode/src/project/instance") - server = servermod.Server.listen({ port: serverPort, hostname: "127.0.0.1" }) + server = await servermod.Server.listen({ port: serverPort, hostname: "127.0.0.1" }) console.log(`opencode server listening on http://127.0.0.1:${serverPort}`) await waitForHealth(`http://127.0.0.1:${serverPort}/global/health`) diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 00ee1db40ccd..db8f6f54dd8a 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -11,6 +11,7 @@ "test": "bun test --timeout 30000", "test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml", "build": "bun run script/build.ts", + "fix-node-pty": "bun run script/fix-node-pty.ts", "upgrade-opentui": "bun run script/upgrade-opentui.ts", "dev": "bun run --conditions=browser ./src/index.ts", "db": "bun drizzle-kit" @@ -26,6 +27,11 @@ "bun": "./src/storage/db.bun.ts", "node": "./src/storage/db.node.ts", "default": "./src/storage/db.bun.ts" + }, + "#pty": { + "bun": "./src/pty/pty.bun.ts", + "node": "./src/pty/pty.node.ts", + "default": "./src/pty/pty.bun.ts" } }, "devDependencies": { @@ -87,8 +93,13 @@ "@aws-sdk/credential-providers": "3.993.0", "@clack/prompts": "1.0.0-alpha.1", "@effect/platform-node": "catalog:", + "@gitlab/gitlab-ai-provider": "3.6.0", + "@gitlab/opencode-gitlab-auth": "1.3.3", + "@hono/node-server": "1.19.11", + "@hono/node-ws": "1.3.0", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", + "@lydell/node-pty": "1.2.0-beta.10", "@modelcontextprotocol/sdk": "1.27.1", "@npmcli/arborist": "9.4.0", "@octokit/graphql": "9.0.2", diff --git a/packages/opencode/script/build-node.ts b/packages/opencode/script/build-node.ts old mode 100644 new mode 100755 index fc515a67a6b7..0e1d5bcf473d --- a/packages/opencode/script/build-node.ts +++ b/packages/opencode/script/build-node.ts @@ -1,5 +1,6 @@ #!/usr/bin/env bun +import { $ } from "bun" import { Script } from "@opencode-ai/script" import fs from "fs" import path from "path" @@ -8,6 +9,15 @@ import { fileURLToPath } from "url" const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const dir = path.resolve(__dirname, "..") +const root = path.resolve(dir, "../..") + +function linker(): "hoisted" | "isolated" { + // jsonc-parser is only declared in packages/opencode, so its install location + // tells us whether Bun used a hoisted or isolated workspace layout. + if (fs.existsSync(path.join(dir, "node_modules", "jsonc-parser"))) return "isolated" + if (fs.existsSync(path.join(root, "node_modules", "jsonc-parser"))) return "hoisted" + throw new Error("Could not detect Bun linker from jsonc-parser") +} process.chdir(dir) @@ -41,11 +51,16 @@ const migrations = await Promise.all( ) console.log(`Loaded ${migrations.length} migrations`) +const link = linker() + +await $`bun install --linker=${link} --os="*" --cpu="*" @lydell/node-pty@1.2.0-beta.10` + await Bun.build({ target: "node", entrypoints: ["./src/node.ts"], outdir: "./dist", format: "esm", + sourcemap: "linked", external: ["jsonc-parser"], define: { OPENCODE_MIGRATIONS: JSON.stringify(migrations), diff --git a/packages/opencode/script/fix-node-pty.ts b/packages/opencode/script/fix-node-pty.ts new file mode 100755 index 000000000000..93641adbdf3c --- /dev/null +++ b/packages/opencode/script/fix-node-pty.ts @@ -0,0 +1,28 @@ +#!/usr/bin/env bun + +import fs from "fs/promises" +import path from "path" +import { fileURLToPath } from "url" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const dir = path.resolve(__dirname, "..") + +if (process.platform !== "win32") { + const root = path.join(dir, "node_modules", "node-pty", "prebuilds") + const dirs = await fs.readdir(root, { withFileTypes: true }).catch(() => []) + const files = dirs.filter((x) => x.isDirectory()).map((x) => path.join(root, x.name, "spawn-helper")) + const result = await Promise.all( + files.map(async (file) => { + const stat = await fs.stat(file).catch(() => undefined) + if (!stat) return + if ((stat.mode & 0o111) === 0o111) return + await fs.chmod(file, stat.mode | 0o755) + return file + }), + ) + const fixed = result.filter(Boolean) + if (fixed.length) { + console.log(`fixed node-pty permissions for ${fixed.length} helper${fixed.length === 1 ? "" : "s"}`) + } +} diff --git a/packages/opencode/src/cli/cmd/acp.ts b/packages/opencode/src/cli/cmd/acp.ts index 99a9a81ab9cd..2fb9038b0f73 100644 --- a/packages/opencode/src/cli/cmd/acp.ts +++ b/packages/opencode/src/cli/cmd/acp.ts @@ -23,7 +23,7 @@ export const AcpCommand = cmd({ process.env.OPENCODE_CLIENT = "acp" await bootstrap(process.cwd(), async () => { const opts = await resolveNetworkOptions(args) - const server = Server.listen(opts) + const server = await Server.listen(opts) const sdk = createOpencodeClient({ baseUrl: `http://${server.hostname}:${server.port}`, diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index ab51fe8c3e3b..73e7a18a7090 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -15,7 +15,7 @@ export const ServeCommand = cmd({ console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.") } const opts = await resolveNetworkOptions(args) - const server = Server.listen(opts) + const server = await Server.listen(opts) console.log(`opencode server listening on http://${server.hostname}:${server.port}`) await new Promise(() => {}) diff --git a/packages/opencode/src/cli/cmd/web.ts b/packages/opencode/src/cli/cmd/web.ts index 0fe056f21f2f..e656c83d9a7c 100644 --- a/packages/opencode/src/cli/cmd/web.ts +++ b/packages/opencode/src/cli/cmd/web.ts @@ -37,7 +37,7 @@ export const WebCommand = cmd({ UI.println(UI.Style.TEXT_WARNING_BOLD + "! " + "OPENCODE_SERVER_PASSWORD is not set; server is unsecured.") } const opts = await resolveNetworkOptions(args) - const server = Server.listen(opts) + const server = await Server.listen(opts) UI.empty() UI.println(UI.logo(" ")) UI.empty() diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index df69c8eba708..d84d1cc7b613 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -130,7 +130,8 @@ export namespace Plugin { get serverUrl(): URL { return Server.url ?? new URL("http://localhost:4096") }, - $: Bun.$, + // @ts-expect-error + $: typeof Bun === "undefined" ? undefined : Bun.$, } for (const plugin of INTERNAL_PLUGINS) { diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index a97f3373d1fd..0321b9800ba5 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -3,7 +3,7 @@ import { Bus } from "@/bus" import { InstanceState } from "@/effect/instance-state" import { makeRuntime } from "@/effect/run-service" import { Instance } from "@/project/instance" -import { type IPty } from "bun-pty" +import type { Proc } from "#pty" import z from "zod" import { Log } from "../util/log" import { lazy } from "@opencode-ai/util/lazy" @@ -26,9 +26,11 @@ export namespace Pty { close: (code?: number, reason?: string) => void } + const sock = (ws: Socket) => (ws.data && typeof ws.data === "object" ? ws.data : ws) + type Active = { info: Info - process: IPty + process: Proc buffer: string bufferCursor: number cursor: number @@ -50,10 +52,7 @@ export namespace Pty { return out } - const pty = lazy(async () => { - const { spawn } = await import("bun-pty") - return spawn - }) + const pty = lazy(() => import("#pty")) export const Info = z .object({ @@ -124,9 +123,9 @@ export namespace Pty { try { session.process.kill() } catch {} - for (const [key, ws] of session.subscribers.entries()) { + for (const [sub, ws] of session.subscribers.entries()) { try { - if (ws.data === key) ws.close() + if (sock(ws) === sub) ws.close() } catch {} } session.subscribers.clear() @@ -198,7 +197,7 @@ export namespace Pty { } log.info("creating session", { id, cmd: command, args, cwd }) - const spawn = yield* Effect.promise(() => pty()) + const { spawn } = yield* Effect.promise(() => pty()) const proc = yield* Effect.sync(() => spawn(command, args, { name: "xterm-256color", @@ -234,7 +233,7 @@ export namespace Pty { session.subscribers.delete(key) continue } - if (ws.data !== key) { + if (sock(ws) !== key) { session.subscribers.delete(key) continue } @@ -304,15 +303,12 @@ export namespace Pty { } log.info("client connected to session", { id }) - // Use ws.data as the unique key for this connection lifecycle. - // If ws.data is undefined, fallback to ws object. - const key = ws.data && typeof ws.data === "object" ? ws.data : ws - // Optionally cleanup if the key somehow exists - session.subscribers.delete(key) - session.subscribers.set(key, ws) + const sub = sock(ws) + session.subscribers.delete(sub) + session.subscribers.set(sub, ws) const cleanup = () => { - session.subscribers.delete(key) + session.subscribers.delete(sub) } const start = session.bufferCursor diff --git a/packages/opencode/src/pty/pty.bun.ts b/packages/opencode/src/pty/pty.bun.ts new file mode 100644 index 000000000000..1f8ce8e4546b --- /dev/null +++ b/packages/opencode/src/pty/pty.bun.ts @@ -0,0 +1,26 @@ +import { spawn as create } from "bun-pty" +import type { Opts, Proc } from "./pty" + +export type { Disp, Exit, Opts, Proc } from "./pty" + +export function spawn(file: string, args: string[], opts: Opts): Proc { + const pty = create(file, args, opts) + return { + pid: pty.pid, + onData(listener) { + return pty.onData(listener) + }, + onExit(listener) { + return pty.onExit(listener) + }, + write(data) { + pty.write(data) + }, + resize(cols, rows) { + pty.resize(cols, rows) + }, + kill(signal) { + pty.kill(signal) + }, + } +} diff --git a/packages/opencode/src/pty/pty.node.ts b/packages/opencode/src/pty/pty.node.ts new file mode 100644 index 000000000000..b45c5bf50985 --- /dev/null +++ b/packages/opencode/src/pty/pty.node.ts @@ -0,0 +1,27 @@ +/** @ts-expect-error */ +import * as pty from "@lydell/node-pty" +import type { Opts, Proc } from "./pty" + +export type { Disp, Exit, Opts, Proc } from "./pty" + +export function spawn(file: string, args: string[], opts: Opts): Proc { + const proc = pty.spawn(file, args, opts) + return { + pid: proc.pid, + onData(listener) { + return proc.onData(listener) + }, + onExit(listener) { + return proc.onExit(listener) + }, + write(data) { + proc.write(data) + }, + resize(cols, rows) { + proc.resize(cols, rows) + }, + kill(signal) { + proc.kill(signal) + }, + } +} diff --git a/packages/opencode/src/pty/pty.ts b/packages/opencode/src/pty/pty.ts new file mode 100644 index 000000000000..fbd1710e52a1 --- /dev/null +++ b/packages/opencode/src/pty/pty.ts @@ -0,0 +1,25 @@ +export type Disp = { + dispose(): void +} + +export type Exit = { + exitCode: number + signal?: number | string +} + +export type Opts = { + name: string + cols?: number + rows?: number + cwd?: string + env?: Record +} + +export type Proc = { + pid: number + onData(listener: (data: string) => void): Disp + onExit(listener: (event: Exit) => void): Disp + write(data: string): void + resize(cols: number, rows: number): void + kill(signal?: string): void +} diff --git a/packages/opencode/src/server/instance.ts b/packages/opencode/src/server/instance.ts index 0186bf46727c..7cc7886b045d 100644 --- a/packages/opencode/src/server/instance.ts +++ b/packages/opencode/src/server/instance.ts @@ -1,6 +1,7 @@ import { describeRoute, resolver, validator } from "hono-openapi" import { Hono } from "hono" import { proxy } from "hono/proxy" +import type { UpgradeWebSocket } from "hono/ws" import z from "zod" import { createHash } from "node:crypto" import { Log } from "../util/log" @@ -41,11 +42,11 @@ const DEFAULT_CSP = const csp = (hash = "") => `default-src 'self'; script-src 'self' 'wasm-unsafe-eval'${hash ? ` 'sha256-${hash}'` : ""}; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:` -export const InstanceRoutes = (app?: Hono) => - (app ?? new Hono()) +export const InstanceRoutes = (upgrade: UpgradeWebSocket, app: Hono = new Hono()) => + app .onError(errorHandler(log)) .route("/project", ProjectRoutes()) - .route("/pty", PtyRoutes()) + .route("/pty", PtyRoutes(upgrade)) .route("/config", ConfigRoutes()) .route("/experimental", ExperimentalRoutes()) .route("/session", SessionRoutes()) diff --git a/packages/opencode/src/server/router.ts b/packages/opencode/src/server/router.ts index f64180892ee3..b239c6272847 100644 --- a/packages/opencode/src/server/router.ts +++ b/packages/opencode/src/server/router.ts @@ -1,4 +1,5 @@ import type { MiddlewareHandler } from "hono" +import type { UpgradeWebSocket } from "hono/ws" import { getAdaptor } from "@/control-plane/adaptors" import { WorkspaceID } from "@/control-plane/schema" import { Workspace } from "@/control-plane/workspace" @@ -24,76 +25,78 @@ function local(method: string, path: string) { return false } -const routes = lazy(() => InstanceRoutes()) +export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): MiddlewareHandler { + const routes = lazy(() => InstanceRoutes(upgrade)) -export const WorkspaceRouterMiddleware: MiddlewareHandler = async (c) => { - const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd() - const directory = Filesystem.resolve( - (() => { - try { - return decodeURIComponent(raw) - } catch { - return raw - } - })(), - ) + return async (c) => { + const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd() + const directory = Filesystem.resolve( + (() => { + try { + return decodeURIComponent(raw) + } catch { + return raw + } + })(), + ) - const url = new URL(c.req.url) - const workspaceParam = url.searchParams.get("workspace") + const url = new URL(c.req.url) + const workspaceParam = url.searchParams.get("workspace") - // TODO: If session is being routed, force it to lookup the - // project/workspace + // TODO: If session is being routed, force it to lookup the + // project/workspace - // If no workspace is provided we use the "project" workspace - if (!workspaceParam) { - return Instance.provide({ - directory, - init: InstanceBootstrap, - async fn() { - return routes().fetch(c.req.raw, c.env) - }, - }) - } + // If no workspace is provided we use the "project" workspace + if (!workspaceParam) { + return Instance.provide({ + directory, + init: InstanceBootstrap, + async fn() { + return routes().fetch(c.req.raw, c.env) + }, + }) + } - const workspaceID = WorkspaceID.make(workspaceParam) - const workspace = await Workspace.get(workspaceID) - if (!workspace) { - return new Response(`Workspace not found: ${workspaceID}`, { - status: 500, - headers: { - "content-type": "text/plain; charset=utf-8", - }, - }) - } + const workspaceID = WorkspaceID.make(workspaceParam) + const workspace = await Workspace.get(workspaceID) + if (!workspace) { + return new Response(`Workspace not found: ${workspaceID}`, { + status: 500, + headers: { + "content-type": "text/plain; charset=utf-8", + }, + }) + } - // Handle local workspaces directly so we can pass env to `fetch`, - // necessary for websocket upgrades - if (workspace.type === "worktree") { - return Instance.provide({ - directory: workspace.directory!, - init: InstanceBootstrap, - async fn() { - return routes().fetch(c.req.raw, c.env) - }, - }) - } + // Handle local workspaces directly so we can pass env to `fetch`, + // necessary for websocket upgrades + if (workspace.type === "worktree") { + return Instance.provide({ + directory: workspace.directory!, + init: InstanceBootstrap, + async fn() { + return routes().fetch(c.req.raw, c.env) + }, + }) + } - // Remote workspaces + // Remote workspaces - if (local(c.req.method, url.pathname)) { - // No instance provided because we are serving cached data; there - // is no instance to work with - return routes().fetch(c.req.raw, c.env) - } + if (local(c.req.method, url.pathname)) { + // No instance provided because we are serving cached data; there + // is no instance to work with + return routes().fetch(c.req.raw, c.env) + } - const adaptor = await getAdaptor(workspace.type) - const headers = new Headers(c.req.raw.headers) - headers.delete("x-opencode-workspace") + const adaptor = await getAdaptor(workspace.type) + const headers = new Headers(c.req.raw.headers) + headers.delete("x-opencode-workspace") - return adaptor.fetch(workspace, `${url.pathname}${url.search}`, { - method: c.req.method, - body: c.req.method === "GET" || c.req.method === "HEAD" ? undefined : await c.req.raw.arrayBuffer(), - signal: c.req.raw.signal, - headers, - }) + return adaptor.fetch(workspace, `${url.pathname}${url.search}`, { + method: c.req.method, + body: c.req.method === "GET" || c.req.method === "HEAD" ? undefined : await c.req.raw.arrayBuffer(), + signal: c.req.raw.signal, + headers, + }) + } } diff --git a/packages/opencode/src/server/routes/pty.ts b/packages/opencode/src/server/routes/pty.ts index de79801e2834..c333f4dd69d3 100644 --- a/packages/opencode/src/server/routes/pty.ts +++ b/packages/opencode/src/server/routes/pty.ts @@ -1,15 +1,14 @@ -import { Hono } from "hono" +import { Hono, type MiddlewareHandler } from "hono" import { describeRoute, validator, resolver } from "hono-openapi" -import { upgradeWebSocket } from "hono/bun" +import type { UpgradeWebSocket } from "hono/ws" import z from "zod" import { Pty } from "@/pty" import { PtyID } from "@/pty/schema" import { NotFoundError } from "../../storage/db" import { errors } from "../error" -import { lazy } from "../../util/lazy" -export const PtyRoutes = lazy(() => - new Hono() +export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { + return new Hono() .get( "/", describeRoute({ @@ -207,5 +206,5 @@ export const PtyRoutes = lazy(() => }, } }), - ), -) + ) +} diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index ec245ed59f29..3822da71eba1 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -4,12 +4,14 @@ import { Hono } from "hono" import { compress } from "hono/compress" import { cors } from "hono/cors" import { basicAuth } from "hono/basic-auth" +import type { UpgradeWebSocket } from "hono/ws" import z from "zod" import { Auth } from "../auth" import { Flag } from "../flag/flag" import { ProviderID } from "../provider/schema" +import { createAdaptorServer, type ServerType } from "@hono/node-server" +import { createNodeWebSocket } from "@hono/node-ws" import { WorkspaceRouterMiddleware } from "./router" -import { websocket } from "hono/bun" import { errors } from "./error" import { GlobalRoutes } from "./routes/global" import { MDNS } from "./mdns" @@ -24,8 +26,14 @@ globalThis.AI_SDK_LOG_WARNINGS = false initProjectors() export namespace Server { - const log = Log.create({ service: "server" }) + export type Listener = { + hostname: string + port: number + url: URL + stop: (close?: boolean) => Promise + } + const log = Log.create({ service: "server" }) const zipped = compress() const skipCompress = (path: string, method: string) => { @@ -34,10 +42,9 @@ export namespace Server { return false } - export const Default = lazy(() => ControlPlaneRoutes()) + export const Default = lazy(() => create({}).app) - export const ControlPlaneRoutes = (opts?: { cors?: string[] }): Hono => { - const app = new Hono() + export function ControlPlaneRoutes(upgrade: UpgradeWebSocket, app = new Hono(), opts?: { cors?: string[] }): Hono { return app .onError(errorHandler(log)) .use((c, next) => { @@ -62,9 +69,7 @@ export namespace Server { path: c.req.path, }) await next() - if (!skip) { - timer.stop() - } + if (!skip) timer.stop() }) .use( cors({ @@ -81,15 +86,8 @@ export namespace Server { ) return input - // *.opencode.ai (https only, adjust if needed) - if (/^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/.test(input)) { - return input - } - if (opts?.cors?.includes(input)) { - return input - } - - return + if (/^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/.test(input)) return input + if (opts?.cors?.includes(input)) return input }, }), ) @@ -234,11 +232,20 @@ export namespace Server { return c.json(true) }, ) - .use(WorkspaceRouterMiddleware) + .use(WorkspaceRouterMiddleware(upgrade)) + } + + function create(opts: { cors?: string[] }) { + const app = new Hono() + const ws = createNodeWebSocket({ app }) + return { + app: ControlPlaneRoutes(ws.upgradeWebSocket, app, opts), + ws, + } } export function createApp(opts: { cors?: string[] }) { - return ControlPlaneRoutes(opts) + return create(opts).app } export async function openapi() { @@ -246,8 +253,8 @@ export namespace Server { // hono-openapi can see describeRoute metadata (`.route()` wraps // handlers when the sub-app has a custom errorHandler, which // strips the metadata symbol). - const app = ControlPlaneRoutes() - InstanceRoutes(app) + const { app, ws } = create({}) + InstanceRoutes(ws.upgradeWebSocket, app) const result = await generateSpecs(app, { documentation: { info: { @@ -261,52 +268,86 @@ export namespace Server { return result } - /** @deprecated do not use this dumb shit */ export let url: URL - export function listen(opts: { + export async function listen(opts: { port: number hostname: string mdns?: boolean mdnsDomain?: string cors?: string[] - }) { - url = new URL(`http://${opts.hostname}:${opts.port}`) - const app = ControlPlaneRoutes({ cors: opts.cors }) - const args = { - hostname: opts.hostname, - idleTimeout: 0, - fetch: app.fetch, - websocket: websocket, - } as const - const tryServe = (port: number) => { - try { - return Bun.serve({ ...args, port }) - } catch { - return undefined - } + }): Promise { + const built = create(opts) + const start = (port: number) => + new Promise((resolve, reject) => { + const server = createAdaptorServer({ fetch: built.app.fetch }) + built.ws.injectWebSocket(server) + const fail = (err: Error) => { + cleanup() + reject(err) + } + const ready = () => { + cleanup() + resolve(server) + } + const cleanup = () => { + server.off("error", fail) + server.off("listening", ready) + } + server.once("error", fail) + server.once("listening", ready) + server.listen(port, opts.hostname) + }) + + const server = opts.port === 0 ? await start(4096).catch(() => start(0)) : await start(opts.port) + const addr = server.address() + if (!addr || typeof addr === "string") { + throw new Error(`Failed to resolve server address for port ${opts.port}`) } - const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) : tryServe(opts.port) - if (!server) throw new Error(`Failed to start server on port ${opts.port}`) - const shouldPublishMDNS = + const next = new URL("http://localhost") + next.hostname = opts.hostname + next.port = String(addr.port) + url = next + + const mdns = opts.mdns && - server.port && + addr.port && opts.hostname !== "127.0.0.1" && opts.hostname !== "localhost" && opts.hostname !== "::1" - if (shouldPublishMDNS) { - MDNS.publish(server.port!, opts.mdnsDomain) + if (mdns) { + MDNS.publish(addr.port, opts.mdnsDomain) } else if (opts.mdns) { log.warn("mDNS enabled but hostname is loopback; skipping mDNS publish") } - const originalStop = server.stop.bind(server) - server.stop = async (closeActiveConnections?: boolean) => { - if (shouldPublishMDNS) MDNS.unpublish() - return originalStop(closeActiveConnections) + let closing: Promise | undefined + return { + hostname: opts.hostname, + port: addr.port, + url: next, + stop(close?: boolean) { + closing ??= new Promise((resolve, reject) => { + if (mdns) MDNS.unpublish() + server.close((err) => { + if (err) { + reject(err) + return + } + resolve() + }) + if (close) { + if ("closeAllConnections" in server && typeof server.closeAllConnections === "function") { + server.closeAllConnections() + } + if ("closeIdleConnections" in server && typeof server.closeIdleConnections === "function") { + server.closeIdleConnections() + } + } + }) + return closing + }, } - - return server } } From cba5cf4640e4897f52d844c88a9e34899da44272 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 6 Apr 2026 18:08:04 +0000 Subject: [PATCH 106/616] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index e09f3cf6d18d..2ebdd78ac4ad 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-LRhPPrOKCGUSCEWTpAxPdWKTKVNkg82WrvD25cP3jts=", - "aarch64-linux": "sha256-sbNxkil47n+B7v6ds5EYFybLytXUyRlu0Cpka0ZmDx4=", - "aarch64-darwin": "sha256-5+99gtpIHGygMW3VBAexNhmaORgI8LCxPk/Gf1fW/ds=", - "x86_64-darwin": "sha256-LqnvZGGnQaRxIoowOr5gf6lFgDhbgQhVPiAcRTtU6fE=" + "x86_64-linux": "sha256-weR6jLBRU54GpE7MCQODzOZH3vER2xOqLb4+Dh0ipYY=", + "aarch64-linux": "sha256-NIcstlZn/w8gfrTSqcsEG7HoIjW73a+KKvyNrly8DZg=", + "aarch64-darwin": "sha256-sAOgteyMcWVvJQE+TudCr2seJghwiTismGq9A1OmzZc=", + "x86_64-darwin": "sha256-i7fmGHqKPuQLFT23B5LP78nRkUeIspykQfzvCvBY5EM=" } } From 9c76484edc624a96af90d1a495350471d16de23b Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 6 Apr 2026 11:12:43 -0700 Subject: [PATCH 107/616] tweak: ensure copilot anthropic models have same reasoning effort model as copilot cli, also fix qwen incorrectly having variants (#21212) --- packages/opencode/src/provider/transform.ts | 8 +++----- packages/opencode/test/session/llm.test.ts | 5 ++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index c1617da40bb9..f536e04bfaf1 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -375,8 +375,8 @@ export namespace ProviderTransform { id.includes("glm") || id.includes("mistral") || id.includes("kimi") || - // TODO: Remove this after models.dev data is fixed to use "kimi-k2.5" instead of "k2p5" - id.includes("k2p5") + id.includes("k2p5") || + id.includes("qwen") ) return {} @@ -465,9 +465,7 @@ export namespace ProviderTransform { return {} } if (model.id.includes("claude")) { - return { - thinking: { thinking_budget: 4000 }, - } + return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }])) } const copilotEfforts = iife(() => { if (id.includes("5.1-codex-max") || id.includes("5.2") || id.includes("5.3")) diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index bb81aa681c9f..18b704f4d3d1 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -289,10 +289,9 @@ describe("session.llm.stream", () => { throw new Error("Server not initialized") } - const providerID = "alibaba" - const modelID = "qwen-plus" + const providerID = "vivgrid" + const modelID = "gemini-3.1-pro-preview" const fixture = await loadFixture(providerID, modelID) - const provider = fixture.provider const model = fixture.model const request = waitRequest( From 3eec7e4f49b0fb2fec5300ea4179456ae9d7cc95 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 6 Apr 2026 11:13:30 -0700 Subject: [PATCH 108/616] tweak: adjust chat.params hook to allow altering of the maxOutputTokens (#21220) --- packages/opencode/src/session/llm.ts | 13 +++++++------ packages/plugin/src/index.ts | 8 +++++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 1813346cdc93..7ef6269b58d3 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -160,6 +160,11 @@ export namespace LLM { ...input.messages, ] + const maxOutputTokens = + isOpenaiOauth || provider.id.includes("github-copilot") + ? undefined + : ProviderTransform.maxOutputTokens(input.model) + const params = await Plugin.trigger( "chat.params", { @@ -175,6 +180,7 @@ export namespace LLM { : undefined, topP: input.agent.topP ?? ProviderTransform.topP(input.model), topK: ProviderTransform.topK(input.model), + maxOutputTokens, options, }, ) @@ -193,11 +199,6 @@ export namespace LLM { }, ) - const maxOutputTokens = - isOpenaiOauth || provider.id.includes("github-copilot") - ? undefined - : ProviderTransform.maxOutputTokens(input.model) - const tools = await resolveTools(input) // LiteLLM and some Anthropic proxies require the tools parameter to be present @@ -291,7 +292,7 @@ export namespace LLM { activeTools: Object.keys(tools).filter((x) => x !== "invalid"), tools, toolChoice: input.toolChoice, - maxOutputTokens, + maxOutputTokens: params.maxOutputTokens, abortSignal: input.abort, headers: { ...(input.model.providerID.startsWith("opencode") diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index 473cac8a9bff..1afb55daa76e 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -212,7 +212,13 @@ export interface Hooks { */ "chat.params"?: ( input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage }, - output: { temperature: number; topP: number; topK: number; options: Record }, + output: { + temperature: number + topP: number + topK: number + maxOutputTokens: number | undefined + options: Record + }, ) => Promise "chat.headers"?: ( input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage }, From 08b8a9aa7e1633776992b7c181ded50d8501aa69 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 6 Apr 2026 15:43:58 -0700 Subject: [PATCH 109/616] tweak: move the max token exclusions to plugins @rekram1-node (#21225) --- packages/opencode/src/plugin/codex.ts | 5 +++++ packages/opencode/src/plugin/github-copilot/copilot.ts | 8 ++++++++ packages/opencode/src/session/llm.ts | 7 +------ packages/opencode/test/session/llm.test.ts | 3 +-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index 77c00eb4f06e..bdeef9823ff2 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -599,5 +599,10 @@ export async function CodexAuthPlugin(input: PluginInput): Promise { output.headers["User-Agent"] = `opencode/${Installation.VERSION} (${os.platform()} ${os.release()}; ${os.arch()})` output.headers.session_id = input.sessionID }, + "chat.params": async (input, output) => { + if (input.model.providerID !== "openai") return + // Match codex cli + output.maxOutputTokens = undefined + }, } } diff --git a/packages/opencode/src/plugin/github-copilot/copilot.ts b/packages/opencode/src/plugin/github-copilot/copilot.ts index ea759b508bcf..c0425b7efe83 100644 --- a/packages/opencode/src/plugin/github-copilot/copilot.ts +++ b/packages/opencode/src/plugin/github-copilot/copilot.ts @@ -309,6 +309,14 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { }, ], }, + "chat.params": async (incoming, output) => { + if (!incoming.model.providerID.includes("github-copilot")) return + + // Match github copilot cli, omit maxOutputTokens for gpt models + if (incoming.model.api.id.includes("gpt")) { + output.maxOutputTokens = undefined + } + }, "chat.headers": async (incoming, output) => { if (!incoming.model.providerID.includes("github-copilot")) return diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 7ef6269b58d3..7f60c02b1d9f 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -160,11 +160,6 @@ export namespace LLM { ...input.messages, ] - const maxOutputTokens = - isOpenaiOauth || provider.id.includes("github-copilot") - ? undefined - : ProviderTransform.maxOutputTokens(input.model) - const params = await Plugin.trigger( "chat.params", { @@ -180,7 +175,7 @@ export namespace LLM { : undefined, topP: input.agent.topP ?? ProviderTransform.topP(input.model), topK: ProviderTransform.topK(input.model), - maxOutputTokens, + maxOutputTokens: ProviderTransform.maxOutputTokens(input.model), options, }, ) diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index 18b704f4d3d1..82ee8c08106e 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -743,8 +743,7 @@ describe("session.llm.stream", () => { expect((body.reasoning as { effort?: string } | undefined)?.effort).toBe("high") const maxTokens = body.max_output_tokens as number | undefined - const expectedMaxTokens = ProviderTransform.maxOutputTokens(resolved) - expect(maxTokens).toBe(expectedMaxTokens) + expect(maxTokens).toBe(undefined) // match codex cli behavior }, }) }) From 9e11424ab71c48387a610a5d7e7834ba251189b9 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 6 Apr 2026 15:56:11 -0700 Subject: [PATCH 110/616] fix: bump openrouter ai sdk pkg to fix openrouter issues (#21242) --- bun.lock | 6 ++++-- packages/opencode/package.json | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 595482953241..ed52c45b9db0 100644 --- a/bun.lock +++ b/bun.lock @@ -344,7 +344,7 @@ "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", - "@openrouter/ai-sdk-provider": "2.3.3", + "@openrouter/ai-sdk-provider": "2.4.2", "@opentui/core": "0.1.97", "@opentui/solid": "0.1.97", "@parcel/watcher": "2.5.1", @@ -1569,7 +1569,7 @@ "@opencode-ai/web": ["@opencode-ai/web@workspace:packages/web"], - "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.3.3", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-4fVteGkVedc7fGoA9+qJs4tpYwALezMq14m2Sjub3KmyRlksCbK+WJf67NPdGem8+NZrV2tAN42A1NU3+SiV3w=="], + "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.4.2", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-uRQZ4da77gru1I7/lNGJhKbqEIY7o/sPsLlbCM97VY9muGDjM/TaJzuwqIviqKTtXLzF0WDj5qBAi6FhxjvlSg=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], @@ -5563,6 +5563,8 @@ "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.3.3", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-4fVteGkVedc7fGoA9+qJs4tpYwALezMq14m2Sjub3KmyRlksCbK+WJf67NPdGem8+NZrV2tAN42A1NU3+SiV3w=="], + "ajv-keywords/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index db8f6f54dd8a..7e7b3934cf1a 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -109,7 +109,7 @@ "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", - "@openrouter/ai-sdk-provider": "2.3.3", + "@openrouter/ai-sdk-provider": "2.4.2", "@opentui/core": "0.1.97", "@opentui/solid": "0.1.97", "@parcel/watcher": "2.5.1", From 32753ebf0555b751ab5873e365abf559bb3c9969 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 6 Apr 2026 23:42:39 +0000 Subject: [PATCH 111/616] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index 2ebdd78ac4ad..e685e5c0efbc 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-weR6jLBRU54GpE7MCQODzOZH3vER2xOqLb4+Dh0ipYY=", - "aarch64-linux": "sha256-NIcstlZn/w8gfrTSqcsEG7HoIjW73a+KKvyNrly8DZg=", - "aarch64-darwin": "sha256-sAOgteyMcWVvJQE+TudCr2seJghwiTismGq9A1OmzZc=", - "x86_64-darwin": "sha256-i7fmGHqKPuQLFT23B5LP78nRkUeIspykQfzvCvBY5EM=" + "x86_64-linux": "sha256-Lg7G/1N/pPK7hwitgLTDQ7ShnD2wUReBKm3FdE7bAnk=", + "aarch64-linux": "sha256-SAl8AWpvtLnVu5aAcmgVH3lM98v4jwVqyjNAdLjhB4o=", + "aarch64-darwin": "sha256-TSUro/T5kwBPV0ccfO2098VKRMKAjj0Rsg/Hcrt02IE=", + "x86_64-darwin": "sha256-R4hyhBcrE7d/iXSbALWfCkja8C++SEfl9HZPBHPz0lU=" } } From 064a64220f7a247f5fde40ba5a5cf7e97bba9184 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 6 Apr 2026 16:53:27 -0700 Subject: [PATCH 112/616] chore: remove ai-sdk/provider-utils patch and update pkg (#21245) --- bun.lock | 99 ++++++++++++++++++- package.json | 3 +- packages/opencode/package.json | 2 +- patches/@ai-sdk%2Fprovider-utils@4.0.21.patch | 61 ------------ 4 files changed, 96 insertions(+), 69 deletions(-) delete mode 100644 patches/@ai-sdk%2Fprovider-utils@4.0.21.patch diff --git a/bun.lock b/bun.lock index ed52c45b9db0..a3b58f4d1e8d 100644 --- a/bun.lock +++ b/bun.lock @@ -321,7 +321,7 @@ "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", - "@ai-sdk/provider-utils": "4.0.21", + "@ai-sdk/provider-utils": "4.0.23", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", "@ai-sdk/xai": "3.0.75", @@ -654,7 +654,6 @@ "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "@ai-sdk/anthropic@3.0.64": "patches/@ai-sdk%2Fanthropic@3.0.64.patch", - "@ai-sdk/provider-utils@4.0.21": "patches/@ai-sdk%2Fprovider-utils@4.0.21.patch", }, "overrides": { "@types/bun": "catalog:", @@ -682,7 +681,7 @@ "@types/node": "22.13.9", "@types/semver": "7.7.1", "@typescript/native-preview": "7.0.0-dev.20251207.1", - "ai": "6.0.138", + "ai": "6.0.149", "cross-spawn": "7.0.6", "diff": "8.0.2", "dompurify": "3.3.1", @@ -765,7 +764,7 @@ "@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="], - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg=="], "@ai-sdk/togetherai": ["@ai-sdk/togetherai@2.0.41", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-k3p9e3k0/gpDDyTtvafsK4HYR4D/aUQW/kzCwWo1+CzdBU84i4L14gWISC/mv6tgSicMXHcEUd521fPufQwNlg=="], @@ -2417,7 +2416,7 @@ "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], - "ai": ["ai@6.0.138", "", { "dependencies": { "@ai-sdk/gateway": "3.0.80", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-49OfPe0f5uxJ6jUdA5BBXjIinP6+ZdYfAtpF2aEH64GA5wPcxH2rf/TBUQQ0bbamBz/D+TLMV18xilZqOC+zaA=="], + "ai": ["ai@6.0.149", "", { "dependencies": { "@ai-sdk/gateway": "3.0.91", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3asRb/m3ZGH7H4+VTuTgj8eQYJZ9IJUmV0ljLslY92mQp6Zj+NVn4SmFj0TBr2Y/wFBWC3xgn++47tSGOXxdbw=="], "ai-gateway-provider": ["ai-gateway-provider@3.1.2", "", { "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^4.0.62", "@ai-sdk/anthropic": "^3.0.46", "@ai-sdk/azure": "^3.0.31", "@ai-sdk/cerebras": "^2.0.34", "@ai-sdk/cohere": "^3.0.21", "@ai-sdk/deepgram": "^2.0.20", "@ai-sdk/deepseek": "^2.0.20", "@ai-sdk/elevenlabs": "^2.0.20", "@ai-sdk/fireworks": "^2.0.34", "@ai-sdk/google": "^3.0.30", "@ai-sdk/google-vertex": "^4.0.61", "@ai-sdk/groq": "^3.0.24", "@ai-sdk/mistral": "^3.0.20", "@ai-sdk/openai": "^3.0.30", "@ai-sdk/perplexity": "^3.0.19", "@ai-sdk/xai": "^3.0.57", "@openrouter/ai-sdk-provider": "^2.2.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^2.0.0", "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.0", "ai": "^6.0.0" } }, "sha512-krGNnJSoO/gJ7Hbe5nQDlsBpDUGIBGtMQTRUaW7s1MylsfvLduba0TLWzQaGtOmNRkP0pGhtGlwsnS6FNQMlyw=="], @@ -5053,8 +5052,50 @@ "@actions/http-client/undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="], + "@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/deepgram/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/deepinfra/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/elevenlabs/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/fireworks/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + "@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@ai-sdk/togetherai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + + "@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + "@astrojs/check/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "@astrojs/cloudflare/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], @@ -5563,6 +5604,8 @@ "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.91", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-J39Dh6Gyg6HjG3A7OFKnJMp3QyZ3Eex+XDiX8aFBdRwwZm3jGWaMhkCxQPH7yiQ9kRiErZwHXX/Oexx4SyGGGA=="], + "ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.3.3", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-4fVteGkVedc7fGoA9+qJs4tpYwALezMq14m2Sjub3KmyRlksCbK+WJf67NPdGem8+NZrV2tAN42A1NU3+SiV3w=="], "ajv-keywords/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], @@ -5913,6 +5956,8 @@ "uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "venice-ai-sdk-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + "vite-plugin-icons-spritesheet/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], "vitest/@vitest/expect": ["@vitest/expect@4.1.2", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.2", "@vitest/utils": "4.1.2", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ=="], @@ -5959,6 +6004,48 @@ "@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], + "@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/azure/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/cerebras/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/cohere/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/deepgram/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/deepinfra/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/deepseek/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/elevenlabs/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/fireworks/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/gateway/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/google-vertex/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/google/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/groq/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/mistral/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/openai-compatible/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/perplexity/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/togetherai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@astrojs/check/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "@astrojs/check/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -6601,6 +6688,8 @@ "type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "venice-ai-sdk-provider/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "vite-plugin-icons-spritesheet/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "vitest/@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], diff --git a/package.json b/package.json index e4a2b5844f55..0c75547d1a0b 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-orm": "1.0.0-beta.19-d95b7a4", "effect": "4.0.0-beta.43", - "ai": "6.0.138", + "ai": "6.0.149", "cross-spawn": "7.0.6", "hono": "4.10.7", "hono-openapi": "1.1.2", @@ -119,7 +119,6 @@ "patchedDependencies": { "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", - "@ai-sdk/provider-utils@4.0.21": "patches/@ai-sdk%2Fprovider-utils@4.0.21.patch", "@ai-sdk/anthropic@3.0.64": "patches/@ai-sdk%2Fanthropic@3.0.64.patch" } } diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 7e7b3934cf1a..cbeaca05f907 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -86,7 +86,7 @@ "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", - "@ai-sdk/provider-utils": "4.0.21", + "@ai-sdk/provider-utils": "4.0.23", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", "@ai-sdk/xai": "3.0.75", diff --git a/patches/@ai-sdk%2Fprovider-utils@4.0.21.patch b/patches/@ai-sdk%2Fprovider-utils@4.0.21.patch deleted file mode 100644 index b93092c46611..000000000000 --- a/patches/@ai-sdk%2Fprovider-utils@4.0.21.patch +++ /dev/null @@ -1,61 +0,0 @@ -diff --git a/dist/index.js b/dist/index.js -index 9aa8e83684777e860d905ff7a6895995a7347a4f..820797581ac2a33e731e139da3ebc98b4d93fdcf 100644 ---- a/dist/index.js -+++ b/dist/index.js -@@ -395,10 +395,13 @@ function validateDownloadUrl(url) { - message: `Invalid URL: ${url}` - }); - } -+ if (parsed.protocol === "data:") { -+ return; -+ } - if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { - throw new DownloadError({ - url, -- message: `URL scheme must be http or https, got ${parsed.protocol}` -+ message: `URL scheme must be http, https, or data, got ${parsed.protocol}` - }); - } - const hostname = parsed.hostname; -diff --git a/dist/index.mjs b/dist/index.mjs -index 095fdc188b1d7f227b42591c78ecb71fe2e2cf8b..ca5227d3b6e358aea8ecd85782a0a2b48130a2c9 100644 ---- a/dist/index.mjs -+++ b/dist/index.mjs -@@ -299,10 +299,13 @@ function validateDownloadUrl(url) { - message: `Invalid URL: ${url}` - }); - } -+ if (parsed.protocol === "data:") { -+ return; -+ } - if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { - throw new DownloadError({ - url, -- message: `URL scheme must be http or https, got ${parsed.protocol}` -+ message: `URL scheme must be http, https, or data, got ${parsed.protocol}` - }); - } - const hostname = parsed.hostname; -diff --git a/src/validate-download-url.ts b/src/validate-download-url.ts -index 7c026ad6b400aef551ce3a424c343e1cedc60997..6a2f11398e58f80a8e11995ac1ce5f4d7c110561 100644 ---- a/src/validate-download-url.ts -+++ b/src/validate-download-url.ts -@@ -18,11 +18,16 @@ export function validateDownloadUrl(url: string): void { - }); - } - -- // Only allow http and https protocols -+ // data: URLs are inline content and do not make network requests. -+ if (parsed.protocol === 'data:') { -+ return; -+ } -+ -+ // Only allow http and https network protocols - if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { - throw new DownloadError({ - url, -- message: `URL scheme must be http or https, got ${parsed.protocol}`, -+ message: `URL scheme must be http, https, or data, got ${parsed.protocol}`, - }); - } - From 2fb6caa107b601a6dfacd580ab018ec9bbe05c5b Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 7 Apr 2026 00:30:30 +0000 Subject: [PATCH 113/616] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index e685e5c0efbc..3aca064e94a0 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-Lg7G/1N/pPK7hwitgLTDQ7ShnD2wUReBKm3FdE7bAnk=", - "aarch64-linux": "sha256-SAl8AWpvtLnVu5aAcmgVH3lM98v4jwVqyjNAdLjhB4o=", - "aarch64-darwin": "sha256-TSUro/T5kwBPV0ccfO2098VKRMKAjj0Rsg/Hcrt02IE=", - "x86_64-darwin": "sha256-R4hyhBcrE7d/iXSbALWfCkja8C++SEfl9HZPBHPz0lU=" + "x86_64-linux": "sha256-N9VjIpWT7/9mKHGlhVP72tiXeogSrwzcCnEw4fRO7jk=", + "aarch64-linux": "sha256-F3+fg/yifj92BjiCSMhgOZnzNLQIDIKjyG3NghHe8CI=", + "aarch64-darwin": "sha256-/Rtpblyh6xg+lY608scgJDuCwY52HB87l9RVtQv9w8U=", + "x86_64-darwin": "sha256-JU9vVRmWOIJYxihktlNQ4RAopDSfjW8yZipLGK1cyx0=" } } From bc4d9778e668444b7016b13d80f91972e7e6611b Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 6 Apr 2026 18:28:32 -0700 Subject: [PATCH 114/616] chore: bump anthropic ai sdk pkg, delete patch (#21247) --- bun.lock | 5 +- package.json | 3 +- packages/opencode/package.json | 2 +- packages/opencode/test/session/llm.test.ts | 9 +- patches/@ai-sdk%2Fanthropic@3.0.64.patch | 119 --------------------- 5 files changed, 9 insertions(+), 129 deletions(-) delete mode 100644 patches/@ai-sdk%2Fanthropic@3.0.64.patch diff --git a/bun.lock b/bun.lock index a3b58f4d1e8d..1efcdee0ad5e 100644 --- a/bun.lock +++ b/bun.lock @@ -307,7 +307,7 @@ "@actions/github": "6.0.1", "@agentclientprotocol/sdk": "0.16.1", "@ai-sdk/amazon-bedrock": "4.0.83", - "@ai-sdk/anthropic": "3.0.64", + "@ai-sdk/anthropic": "3.0.67", "@ai-sdk/azure": "3.0.49", "@ai-sdk/cerebras": "2.0.41", "@ai-sdk/cohere": "3.0.27", @@ -653,7 +653,6 @@ "patchedDependencies": { "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", - "@ai-sdk/anthropic@3.0.64": "patches/@ai-sdk%2Fanthropic@3.0.64.patch", }, "overrides": { "@types/bun": "catalog:", @@ -5818,6 +5817,8 @@ "nypm/tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="], + "opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.67", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FFX4P5Fd6lcQJc2OLngZQkbbJHa0IDDZi087Edb8qRZx6h90krtM61ArbMUL8us/7ZUwojCXnyJ/wQ2Eflx2jQ=="], + "opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="], "opencontrol/@tsconfig/bun": ["@tsconfig/bun@1.0.7", "", {}, "sha512-udGrGJBNQdXGVulehc1aWT73wkR9wdaGBtB6yL70RJsqwW/yJhIg6ZbRlPOfIUiFNrnBuYLBi9CSmMKfDC7dvA=="], diff --git a/package.json b/package.json index 0c75547d1a0b..4ce36d17ecd1 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,6 @@ }, "patchedDependencies": { "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", - "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", - "@ai-sdk/anthropic@3.0.64": "patches/@ai-sdk%2Fanthropic@3.0.64.patch" + "solid-js@1.9.10": "patches/solid-js@1.9.10.patch" } } diff --git a/packages/opencode/package.json b/packages/opencode/package.json index cbeaca05f907..ce90fe3d8496 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -72,7 +72,7 @@ "@actions/github": "6.0.1", "@agentclientprotocol/sdk": "0.16.1", "@ai-sdk/amazon-bedrock": "4.0.83", - "@ai-sdk/anthropic": "3.0.64", + "@ai-sdk/anthropic": "3.0.67", "@ai-sdk/azure": "3.0.49", "@ai-sdk/cerebras": "2.0.41", "@ai-sdk/cohere": "3.0.27", diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index 82ee8c08106e..946797da50e2 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -872,16 +872,15 @@ describe("session.llm.stream", () => { }) }) - test("sends messages API payload for Anthropic models", async () => { + test("sends messages API payload for Anthropic Compatible models", async () => { const server = state.server if (!server) { throw new Error("Server not initialized") } - const providerID = "anthropic" - const modelID = "claude-3-5-sonnet-20241022" + const providerID = "minimax" + const modelID = "MiniMax-M2.5" const fixture = await loadFixture(providerID, modelID) - const provider = fixture.provider const model = fixture.model const chunks = [ @@ -962,7 +961,7 @@ describe("session.llm.stream", () => { role: "user", time: { created: Date.now() }, agent: agent.name, - model: { providerID: ProviderID.make("minimax"), modelID: ModelID.make("MiniMax-M2.7") }, + model: { providerID: ProviderID.make("minimax"), modelID: ModelID.make("MiniMax-M2.5") }, } satisfies MessageV2.User const stream = await LLM.stream({ diff --git a/patches/@ai-sdk%2Fanthropic@3.0.64.patch b/patches/@ai-sdk%2Fanthropic@3.0.64.patch deleted file mode 100644 index b8c2f387d7f7..000000000000 --- a/patches/@ai-sdk%2Fanthropic@3.0.64.patch +++ /dev/null @@ -1,119 +0,0 @@ ---- a/dist/index.js -+++ b/dist/index.js -@@ -3155,15 +3155,6 @@ - }); - } - baseArgs.max_tokens = maxTokens + (thinkingBudget != null ? thinkingBudget : 0); -- } else { -- if (topP != null && temperature != null) { -- warnings.push({ -- type: "unsupported", -- feature: "topP", -- details: `topP is not supported when temperature is set. topP is ignored.` -- }); -- baseArgs.top_p = void 0; -- } - } - if (isKnownModel && baseArgs.max_tokens > maxOutputTokensForModel) { - if (maxOutputTokens != null) { -@@ -5180,4 +5171,4 @@ - createAnthropic, - forwardAnthropicContainerIdFromLastStep - }); --//# sourceMappingURL=index.js.map -\ No newline at end of file -+//# sourceMappingURL=index.js.map ---- a/dist/index.mjs -+++ b/dist/index.mjs -@@ -3192,15 +3192,6 @@ - }); - } - baseArgs.max_tokens = maxTokens + (thinkingBudget != null ? thinkingBudget : 0); -- } else { -- if (topP != null && temperature != null) { -- warnings.push({ -- type: "unsupported", -- feature: "topP", -- details: `topP is not supported when temperature is set. topP is ignored.` -- }); -- baseArgs.top_p = void 0; -- } - } - if (isKnownModel && baseArgs.max_tokens > maxOutputTokensForModel) { - if (maxOutputTokens != null) { -@@ -5256,4 +5247,4 @@ - createAnthropic, - forwardAnthropicContainerIdFromLastStep - }; --//# sourceMappingURL=index.mjs.map -\ No newline at end of file -+//# sourceMappingURL=index.mjs.map ---- a/dist/internal/index.js -+++ b/dist/internal/index.js -@@ -3147,15 +3147,6 @@ - }); - } - baseArgs.max_tokens = maxTokens + (thinkingBudget != null ? thinkingBudget : 0); -- } else { -- if (topP != null && temperature != null) { -- warnings.push({ -- type: "unsupported", -- feature: "topP", -- details: `topP is not supported when temperature is set. topP is ignored.` -- }); -- baseArgs.top_p = void 0; -- } - } - if (isKnownModel && baseArgs.max_tokens > maxOutputTokensForModel) { - if (maxOutputTokens != null) { -@@ -5080,4 +5071,4 @@ - anthropicTools, - prepareTools - }); --//# sourceMappingURL=index.js.map -\ No newline at end of file -+//# sourceMappingURL=index.js.map ---- a/dist/internal/index.mjs -+++ b/dist/internal/index.mjs -@@ -3176,15 +3176,6 @@ - }); - } - baseArgs.max_tokens = maxTokens + (thinkingBudget != null ? thinkingBudget : 0); -- } else { -- if (topP != null && temperature != null) { -- warnings.push({ -- type: "unsupported", -- feature: "topP", -- details: `topP is not supported when temperature is set. topP is ignored.` -- }); -- baseArgs.top_p = void 0; -- } - } - if (isKnownModel && baseArgs.max_tokens > maxOutputTokensForModel) { - if (maxOutputTokens != null) { -@@ -5148,4 +5139,4 @@ - anthropicTools, - prepareTools - }; --//# sourceMappingURL=index.mjs.map -\ No newline at end of file -+//# sourceMappingURL=index.mjs.map ---- a/src/anthropic-messages-language-model.ts -+++ b/src/anthropic-messages-language-model.ts -@@ -534,16 +534,6 @@ - - // adjust max tokens to account for thinking: - baseArgs.max_tokens = maxTokens + (thinkingBudget ?? 0); -- } else { -- // Only check temperature/topP mutual exclusivity when thinking is not enabled -- if (topP != null && temperature != null) { -- warnings.push({ -- type: 'unsupported', -- feature: 'topP', -- details: `topP is not supported when temperature is set. topP is ignored.`, -- }); -- baseArgs.top_p = undefined; -- } - } - - // limit to max output tokens for known models to enable model switching without breaking it: From c144003df0e79f49e67c8d7488c13f8817be2b8b Mon Sep 17 00:00:00 2001 From: James Long Date: Mon, 6 Apr 2026 23:19:55 -0400 Subject: [PATCH 115/616] refactor(core): add full http proxy and change workspace adaptor interface (#21239) --- .../src/control-plane/adaptors/worktree.ts | 8 +- packages/opencode/src/control-plane/types.ts | 15 +- .../opencode/src/control-plane/workspace.ts | 18 ++- packages/opencode/src/server/proxy.ts | 130 ++++++++++++++++++ packages/opencode/src/server/router.ts | 31 +++-- 5 files changed, 182 insertions(+), 20 deletions(-) create mode 100644 packages/opencode/src/server/proxy.ts diff --git a/packages/opencode/src/control-plane/adaptors/worktree.ts b/packages/opencode/src/control-plane/adaptors/worktree.ts index 719748e3a193..9fb6c7479365 100644 --- a/packages/opencode/src/control-plane/adaptors/worktree.ts +++ b/packages/opencode/src/control-plane/adaptors/worktree.ts @@ -32,7 +32,11 @@ export const WorktreeAdaptor: Adaptor = { const config = Config.parse(info) await Worktree.remove({ directory: config.directory }) }, - async fetch(_info, _input: RequestInfo | URL, _init?: RequestInit) { - throw new Error("fetch not implemented") + target(info) { + const config = Config.parse(info) + return { + type: "local", + directory: config.directory, + } }, } diff --git a/packages/opencode/src/control-plane/types.ts b/packages/opencode/src/control-plane/types.ts index ab628a693814..dd17c56d93db 100644 --- a/packages/opencode/src/control-plane/types.ts +++ b/packages/opencode/src/control-plane/types.ts @@ -13,9 +13,20 @@ export const WorkspaceInfo = z.object({ }) export type WorkspaceInfo = z.infer +export type Target = + | { + type: "local" + directory: string + } + | { + type: "remote" + url: string | URL + headers?: HeadersInit + } + export type Adaptor = { configure(input: WorkspaceInfo): WorkspaceInfo | Promise - create(input: WorkspaceInfo, from?: WorkspaceInfo): Promise + create(config: WorkspaceInfo, from?: WorkspaceInfo): Promise remove(config: WorkspaceInfo): Promise - fetch(config: WorkspaceInfo, input: RequestInfo | URL, init?: RequestInit): Promise + target(config: WorkspaceInfo): Target | Promise } diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index e5294844b148..bb0fd60020bf 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -116,17 +116,31 @@ export namespace Workspace { async function workspaceEventLoop(space: Info, stop: AbortSignal) { while (!stop.aborted) { const adaptor = await getAdaptor(space.type) - const res = await adaptor.fetch(space, "/event", { method: "GET", signal: stop }).catch(() => undefined) - if (!res || !res.ok || !res.body) { + const target = await Promise.resolve(adaptor.target(space)) + + if (target.type === "local") { + return + } + + const baseURL = String(target.url).replace(/\/?$/, "/") + + const res = await fetch(new URL(baseURL + "/event"), { + method: "GET", + signal: stop, + }) + + if (!res.ok || !res.body) { await sleep(1000) continue } + await parseSSE(res.body, stop, (event) => { GlobalBus.emit("event", { directory: space.id, payload: event, }) }) + // Wait 250ms and retry if SSE connection fails await sleep(250) } diff --git a/packages/opencode/src/server/proxy.ts b/packages/opencode/src/server/proxy.ts new file mode 100644 index 000000000000..c489c6b42bf7 --- /dev/null +++ b/packages/opencode/src/server/proxy.ts @@ -0,0 +1,130 @@ +import type { Target } from "@/control-plane/types" +import { lazy } from "@/util/lazy" +import { Hono } from "hono" +import { upgradeWebSocket } from "hono/bun" + +const hop = new Set([ + "connection", + "keep-alive", + "proxy-authenticate", + "proxy-authorization", + "proxy-connection", + "te", + "trailer", + "transfer-encoding", + "upgrade", + "host", +]) + +type Msg = string | ArrayBuffer | Uint8Array + +function headers(req: Request, extra?: HeadersInit) { + const out = new Headers(req.headers) + for (const key of hop) out.delete(key) + out.delete("x-opencode-directory") + out.delete("x-opencode-workspace") + if (!extra) return out + for (const [key, value] of new Headers(extra).entries()) { + out.set(key, value) + } + return out +} + +function protocols(req: Request) { + const value = req.headers.get("sec-websocket-protocol") + if (!value) return [] + return value + .split(",") + .map((item) => item.trim()) + .filter(Boolean) +} + +function socket(url: string | URL) { + const next = new URL(url) + if (next.protocol === "http:") next.protocol = "ws:" + if (next.protocol === "https:") next.protocol = "wss:" + return next.toString() +} + +function send(ws: { send(data: string | ArrayBuffer | Uint8Array): void }, data: any) { + if (data instanceof Blob) { + return data.arrayBuffer().then((x) => ws.send(x)) + } + return ws.send(data) +} + +const app = lazy(() => + new Hono().get( + "/__workspace_ws", + upgradeWebSocket((c) => { + const url = c.req.header("x-opencode-proxy-url") + const queue: Msg[] = [] + let remote: WebSocket | undefined + return { + onOpen(_, ws) { + if (!url) { + ws.close(1011, "missing proxy target") + return + } + remote = new WebSocket(url, protocols(c.req.raw)) + remote.binaryType = "arraybuffer" + remote.onopen = () => { + for (const item of queue) remote?.send(item) + queue.length = 0 + } + remote.onmessage = (event) => { + send(ws, event.data) + } + remote.onerror = () => { + ws.close(1011, "proxy error") + } + remote.onclose = (event) => { + ws.close(event.code, event.reason) + } + }, + onMessage(event) { + const data = event.data + if (typeof data !== "string" && !(data instanceof Uint8Array) && !(data instanceof ArrayBuffer)) return + if (remote?.readyState === WebSocket.OPEN) { + remote.send(data) + return + } + queue.push(data) + }, + onClose(event) { + remote?.close(event.code, event.reason) + }, + } + }), + ), +) + +export namespace ServerProxy { + export function http(target: Extract, req: Request) { + return fetch( + new Request(target.url, { + method: req.method, + headers: headers(req, target.headers), + body: req.method === "GET" || req.method === "HEAD" ? undefined : req.body, + redirect: "manual", + signal: req.signal, + }), + ) + } + + export function websocket(target: Extract, req: Request, env: unknown) { + const url = new URL(req.url) + url.pathname = "/__workspace_ws" + url.search = "" + const next = new Headers(req.headers) + next.set("x-opencode-proxy-url", socket(target.url)) + return app().fetch( + new Request(url, { + method: req.method, + headers: next, + signal: req.signal, + }), + env as never, + ) + } +} diff --git a/packages/opencode/src/server/router.ts b/packages/opencode/src/server/router.ts index b239c6272847..b6f99ec73bd0 100644 --- a/packages/opencode/src/server/router.ts +++ b/packages/opencode/src/server/router.ts @@ -3,6 +3,7 @@ import type { UpgradeWebSocket } from "hono/ws" import { getAdaptor } from "@/control-plane/adaptors" import { WorkspaceID } from "@/control-plane/schema" import { Workspace } from "@/control-plane/workspace" +import { ServerProxy } from "./proxy" import { lazy } from "@/util/lazy" import { Filesystem } from "@/util/filesystem" import { Instance } from "@/project/instance" @@ -41,7 +42,7 @@ export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): Middleware ) const url = new URL(c.req.url) - const workspaceParam = url.searchParams.get("workspace") + const workspaceParam = url.searchParams.get("workspace") || c.req.header("x-opencode-workspace") // TODO: If session is being routed, force it to lookup the // project/workspace @@ -68,11 +69,12 @@ export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): Middleware }) } - // Handle local workspaces directly so we can pass env to `fetch`, - // necessary for websocket upgrades - if (workspace.type === "worktree") { + const adaptor = await getAdaptor(workspace.type) + const target = await adaptor.target(workspace) + + if (target.type === "local") { return Instance.provide({ - directory: workspace.directory!, + directory: target.directory, init: InstanceBootstrap, async fn() { return routes().fetch(c.req.raw, c.env) @@ -80,23 +82,24 @@ export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): Middleware }) } - // Remote workspaces - if (local(c.req.method, url.pathname)) { // No instance provided because we are serving cached data; there // is no instance to work with return routes().fetch(c.req.raw, c.env) } - const adaptor = await getAdaptor(workspace.type) + if (c.req.header("upgrade")?.toLowerCase() === "websocket") { + return ServerProxy.websocket(target, c.req.raw, c.env) + } + const headers = new Headers(c.req.raw.headers) headers.delete("x-opencode-workspace") - return adaptor.fetch(workspace, `${url.pathname}${url.search}`, { - method: c.req.method, - body: c.req.method === "GET" || c.req.method === "HEAD" ? undefined : await c.req.raw.arrayBuffer(), - signal: c.req.raw.signal, - headers, - }) + return ServerProxy.http( + target, + new Request(c.req.raw, { + headers, + }), + ) } } From fffbefb56259ce00380213c2f5edd0104211e861 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 7 Apr 2026 00:16:32 -0400 Subject: [PATCH 116/616] go: add mimo --- .../console/app/src/routes/workspace/[id]/go/lite-section.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx index 2f8ad8aba4fc..20042ca71825 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx @@ -287,6 +287,8 @@ export function LiteSection() {
  • Kimi K2.5
  • GLM-5
  • +
  • Mimo-V2-Pro
  • +
  • Mimo-V2-Omni
  • MiniMax M2.5
  • MiniMax M2.7
From 50cbefb92d58d5a9dd2494e984a9d06a1505a9c6 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:30:05 -0500 Subject: [PATCH 117/616] feat: add --dangerously-skip-permissions flag to opencode run (#21266) --- packages/opencode/src/cli/cmd/run.ts | 31 ++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 92b6156ca7af..05fb3a5798d0 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -302,6 +302,11 @@ export const RunCommand = cmd({ describe: "show thinking blocks", default: false, }) + .option("dangerously-skip-permissions", { + type: "boolean", + describe: "auto-approve permissions that are not explicitly denied (dangerous!)", + default: false, + }) }, handler: async (args) => { let message = [...args.message, ...(args["--"] || [])] @@ -544,15 +549,23 @@ export const RunCommand = cmd({ if (event.type === "permission.asked") { const permission = event.properties if (permission.sessionID !== sessionID) continue - UI.println( - UI.Style.TEXT_WARNING_BOLD + "!", - UI.Style.TEXT_NORMAL + - `permission requested: ${permission.permission} (${permission.patterns.join(", ")}); auto-rejecting`, - ) - await sdk.permission.reply({ - requestID: permission.id, - reply: "reject", - }) + + if (args["dangerously-skip-permissions"]) { + await sdk.permission.reply({ + requestID: permission.id, + reply: "once", + }) + } else { + UI.println( + UI.Style.TEXT_WARNING_BOLD + "!", + UI.Style.TEXT_NORMAL + + `permission requested: ${permission.permission} (${permission.patterns.join(", ")}); auto-rejecting`, + ) + await sdk.permission.reply({ + requestID: permission.id, + reply: "reject", + }) + } } } } From db30ec086e2891a8343d747bf56a8d3105cd87b8 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 7 Apr 2026 04:38:49 +0000 Subject: [PATCH 118/616] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index 3aca064e94a0..0b8e34e78646 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-N9VjIpWT7/9mKHGlhVP72tiXeogSrwzcCnEw4fRO7jk=", - "aarch64-linux": "sha256-F3+fg/yifj92BjiCSMhgOZnzNLQIDIKjyG3NghHe8CI=", - "aarch64-darwin": "sha256-/Rtpblyh6xg+lY608scgJDuCwY52HB87l9RVtQv9w8U=", - "x86_64-darwin": "sha256-JU9vVRmWOIJYxihktlNQ4RAopDSfjW8yZipLGK1cyx0=" + "x86_64-linux": "sha256-r1+AehuOGIOaaxfXkQGracT/6OdFRn5Ub8s7H+MeKFY=", + "aarch64-linux": "sha256-WkMSRF/ZJLyzxNBjpiMR459C9G0NVOEw31tm8roPneA=", + "aarch64-darwin": "sha256-Z127cxFpTl8Ml7PB3CG9TcCU08oYCPuk0FECK2MQ2CI=", + "x86_64-darwin": "sha256-pkRoFtnVjyl+5fm+rrFyRnEwvptxylnFxPAcEv4ZOCg=" } } From 572319802b857760f589e1067584657868229eed Mon Sep 17 00:00:00 2001 From: gitpush-gitpaid <149759805+gitpush-gitpaid@users.noreply.github.com> Date: Tue, 7 Apr 2026 00:39:59 -0400 Subject: [PATCH 119/616] feat(opencode): Add PDF attachment Drag and Drop (#16926) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Aiden Cline --- .../cli/cmd/tui/component/prompt/index.tsx | 36 +++++++++++++------ .../tui/feature-plugins/home/tips-view.tsx | 2 +- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 087742a979d7..2fef184f5799 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -2,6 +2,7 @@ import { BoxRenderable, TextareaRenderable, MouseEvent, PasteEvent, decodePasteB import { createEffect, createMemo, onMount, createSignal, onCleanup, on, Show, Switch, Match } from "solid-js" import "opentui-spinner/solid" import path from "path" +import { fileURLToPath } from "url" import { Filesystem } from "@/util/filesystem" import { useLocal } from "@tui/context/local" import { useTheme } from "@tui/context/theme" @@ -248,7 +249,7 @@ export function Prompt(props: PromptProps) { onSelect: async () => { const content = await Clipboard.read() if (content?.mime.startsWith("image/")) { - await pasteImage({ + await pasteAttachment({ filename: "clipboard", mime: content.mime, content: content.data, @@ -771,11 +772,16 @@ export function Prompt(props: PromptProps) { ) } - async function pasteImage(file: { filename?: string; content: string; mime: string }) { + async function pasteAttachment(file: { filename?: string; filepath?: string; content: string; mime: string }) { const currentOffset = input.visualCursor.offset const extmarkStart = currentOffset - const count = store.prompt.parts.filter((x) => x.type === "file" && x.mime.startsWith("image/")).length - const virtualText = `[Image ${count + 1}]` + const pdf = file.mime === "application/pdf" + const count = store.prompt.parts.filter((x) => { + if (x.type !== "file") return false + if (pdf) return x.mime === "application/pdf" + return x.mime.startsWith("image/") + }).length + const virtualText = pdf ? `[PDF ${count + 1}]` : `[Image ${count + 1}]` const extmarkEnd = extmarkStart + virtualText.length const textToInsert = virtualText + " " @@ -796,7 +802,7 @@ export function Prompt(props: PromptProps) { url: `data:${file.mime};base64,${file.content}`, source: { type: "file", - path: file.filename ?? "", + path: file.filepath ?? file.filename ?? "", text: { start: extmarkStart, end: extmarkEnd, @@ -926,7 +932,7 @@ export function Prompt(props: PromptProps) { const content = await Clipboard.read() if (content?.mime.startsWith("image/")) { e.preventDefault() - await pasteImage({ + await pasteAttachment({ filename: "clipboard", mime: content.mime, content: content.data, @@ -1012,9 +1018,16 @@ export function Prompt(props: PromptProps) { return } - // trim ' from the beginning and end of the pasted content. just - // ' and nothing else - const filepath = pastedContent.replace(/^'+|'+$/g, "").replace(/\\ /g, " ") + const filepath = iife(() => { + const raw = pastedContent.replace(/^['"]+|['"]+$/g, "") + if (raw.startsWith("file://")) { + try { + return fileURLToPath(raw) + } catch {} + } + if (process.platform === "win32") return raw + return raw.replace(/\\(.)/g, "$1") + }) const isUrl = /^(https?):\/\//.test(filepath) if (!isUrl) { try { @@ -1029,14 +1042,15 @@ export function Prompt(props: PromptProps) { return } } - if (mime.startsWith("image/")) { + if (mime.startsWith("image/") || mime === "application/pdf") { event.preventDefault() const content = await Filesystem.readArrayBuffer(filepath) .then((buffer) => Buffer.from(buffer).toString("base64")) .catch(() => {}) if (content) { - await pasteImage({ + await pasteAttachment({ filename, + filepath, mime, content, }) diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx index a87e4ed2b73d..1a9d907bb97e 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx @@ -55,7 +55,7 @@ const TIPS = [ "Use {highlight}/undo{/highlight} to revert the last message and file changes", "Use {highlight}/redo{/highlight} to restore previously undone messages and file changes", "Run {highlight}/share{/highlight} to create a public link to your conversation at opencode.ai", - "Drag and drop images into the terminal to add them as context", + "Drag and drop images or PDFs into the terminal to add them as context", "Press {highlight}Ctrl+V{/highlight} to paste images from your clipboard into the prompt", "Press {highlight}Ctrl+X E{/highlight} or {highlight}/editor{/highlight} to compose messages in your external editor", "Run {highlight}/init{/highlight} to auto-generate project rules based on your codebase", From 1b6826c1825e0d1284a60812b97e188fa57284a4 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Tue, 7 Apr 2026 15:45:22 +0530 Subject: [PATCH 120/616] feat(app): show full names on composer attachment chips (#21306) --- .../prompt-input/image-attachments.tsx | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/packages/app/src/components/prompt-input/image-attachments.tsx b/packages/app/src/components/prompt-input/image-attachments.tsx index 835fddc30710..dd8138e5a4f2 100644 --- a/packages/app/src/components/prompt-input/image-attachments.tsx +++ b/packages/app/src/components/prompt-input/image-attachments.tsx @@ -1,5 +1,6 @@ import { Component, For, Show } from "solid-js" import { Icon } from "@opencode-ai/ui/icon" +import { Tooltip } from "@opencode-ai/ui/tooltip" import type { ImageAttachmentPart } from "@/context/prompt" type PromptImageAttachmentsProps = { @@ -22,34 +23,36 @@ export const PromptImageAttachments: Component = (p
{(attachment) => ( -
- - -
- } - > - {attachment.filename} props.onOpen(attachment)} - /> - - -
- {attachment.filename} + +
+ + +
+ } + > + {attachment.filename} props.onOpen(attachment)} + /> + + +
+ {attachment.filename} +
-
+ )}
From d95ae068a90b32c93fe53da73e0183b5f276bb30 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Tue, 7 Apr 2026 16:17:53 +0530 Subject: [PATCH 121/616] style(app): redesign jump-to-bottom button per figma spec (#21313) --- packages/app/src/pages/session/message-timeline.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index cbabbda72d8e..bc211303a6ac 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -577,10 +577,18 @@ export function MessageTimeline(props: { }} >
Date: Tue, 7 Apr 2026 16:30:13 +0530 Subject: [PATCH 122/616] Move auto-accept permissions to settings (#21308) --- packages/app/e2e/prompt/prompt-shell.spec.ts | 15 ++++--- .../e2e/session/session-composer-dock.spec.ts | 21 +++++----- packages/app/src/components/prompt-input.tsx | 39 +------------------ .../app/src/components/settings-general.tsx | 39 +++++++++++++++++++ 4 files changed, 59 insertions(+), 55 deletions(-) diff --git a/packages/app/e2e/prompt/prompt-shell.spec.ts b/packages/app/e2e/prompt/prompt-shell.spec.ts index 28fa02dcd328..81af4cb1bc5c 100644 --- a/packages/app/e2e/prompt/prompt-shell.spec.ts +++ b/packages/app/e2e/prompt/prompt-shell.spec.ts @@ -1,6 +1,6 @@ import type { ToolPart } from "@opencode-ai/sdk/v2/client" import { test, expect } from "../fixtures" -import { withSession } from "../actions" +import { closeDialog, openSettings, withSession } from "../actions" import { promptModelSelector, promptSelector, promptVariantSelector } from "../selectors" const isBash = (part: unknown): part is ToolPart => { @@ -19,12 +19,15 @@ test("shell mode runs a command in the project directory", async ({ page, projec await withSession(project.sdk, `e2e shell ${Date.now()}`, async (session) => { project.trackSession(session.id) await project.gotoSession(session.id) - const button = page.locator('[data-action="prompt-permissions"]').first() - await expect(button).toBeVisible() - if ((await button.getAttribute("aria-pressed")) !== "true") { - await button.click() - await expect(button).toHaveAttribute("aria-pressed", "true") + const dialog = await openSettings(page) + const toggle = dialog.locator('[data-action="settings-auto-accept-permissions"]').first() + const input = toggle.locator('[data-slot="switch-input"]').first() + await expect(toggle).toBeVisible() + if ((await input.getAttribute("aria-checked")) !== "true") { + await toggle.locator('[data-slot="switch-control"]').click() + await expect(input).toHaveAttribute("aria-checked", "true") } + await closeDialog(page, dialog) await project.shell(cmd) await expect diff --git a/packages/app/e2e/session/session-composer-dock.spec.ts b/packages/app/e2e/session/session-composer-dock.spec.ts index 8eeac5b1a18a..ecacea83dcb8 100644 --- a/packages/app/e2e/session/session-composer-dock.spec.ts +++ b/packages/app/e2e/session/session-composer-dock.spec.ts @@ -5,7 +5,7 @@ import { type ComposerProbeState, type ComposerWindow, } from "../../src/testing/session-composer" -import { cleanupSession, clearSessionDockSeed, seedSessionQuestion } from "../actions" +import { cleanupSession, clearSessionDockSeed, closeDialog, openSettings, seedSessionQuestion } from "../actions" import { permissionDockSelector, promptSelector, @@ -65,12 +65,14 @@ async function clearPermissionDock(page: any, label: RegExp) { } async function setAutoAccept(page: any, enabled: boolean) { - const button = page.locator('[data-action="prompt-permissions"]').first() - await expect(button).toBeVisible() - const pressed = (await button.getAttribute("aria-pressed")) === "true" - if (pressed === enabled) return - await button.click() - await expect(button).toHaveAttribute("aria-pressed", enabled ? "true" : "false") + const dialog = await openSettings(page) + const toggle = dialog.locator('[data-action="settings-auto-accept-permissions"]').first() + const input = toggle.locator('[data-slot="switch-input"]').first() + await expect(toggle).toBeVisible() + const checked = (await input.getAttribute("aria-checked")) === "true" + if (checked !== enabled) await toggle.locator('[data-slot="switch-control"]').click() + await expect(input).toHaveAttribute("aria-checked", enabled ? "true" : "false") + await closeDialog(page, dialog) } async function expectQuestionBlocked(page: any) { @@ -277,6 +279,7 @@ test("default dock shows prompt input", async ({ page, project }) => { await expect(page.locator(sessionComposerDockSelector)).toBeVisible() await expect(page.locator(promptSelector)).toBeVisible() + await expect(page.locator('[data-action="prompt-permissions"]')).toHaveCount(0) await expect(page.locator(questionDockSelector)).toHaveCount(0) await expect(page.locator(permissionDockSelector)).toHaveCount(0) @@ -290,10 +293,6 @@ test("default dock shows prompt input", async ({ page, project }) => { test("auto-accept toggle works before first submit", async ({ page, project }) => { await project.open() - const button = page.locator('[data-action="prompt-permissions"]').first() - await expect(button).toBeVisible() - await expect(button).toHaveAttribute("aria-pressed", "false") - await setAutoAccept(page, true) await setAutoAccept(page, false) }) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index e9049ae7e23e..eedbc91cfdbe 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -1079,17 +1079,6 @@ export const PromptInput: Component = (props) => { if (!id) return permission.isAutoAcceptingDirectory(sdk.directory) return permission.isAutoAccepting(id, sdk.directory) }) - const acceptLabel = createMemo(() => - language.t(accepting() ? "command.permissions.autoaccept.disable" : "command.permissions.autoaccept.enable"), - ) - const toggleAccept = () => { - if (!params.id) { - permission.toggleAutoAcceptDirectory(sdk.directory) - return - } - - permission.toggleAutoAccept(params.id, sdk.directory) - } const { abort, handleSubmit } = createPromptSubmit({ info, @@ -1333,11 +1322,7 @@ export const PromptInput: Component = (props) => { onMouseDown={(e) => { const target = e.target if (!(target instanceof HTMLElement)) return - if ( - target.closest( - '[data-action="prompt-attach"], [data-action="prompt-submit"], [data-action="prompt-permissions"]', - ) - ) { + if (target.closest('[data-action="prompt-attach"], [data-action="prompt-submit"]')) { return } editorRef?.focus() @@ -1597,28 +1582,6 @@ export const PromptInput: Component = (props) => {
- - -
diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 8a4d498866aa..b4ac061df494 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -8,7 +8,9 @@ import { TextField } from "@opencode-ai/ui/text-field" import { Tooltip } from "@opencode-ai/ui/tooltip" import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context" import { showToast } from "@opencode-ai/ui/toast" +import { useParams } from "@solidjs/router" import { useLanguage } from "@/context/language" +import { usePermission } from "@/context/permission" import { usePlatform } from "@/context/platform" import { monoDefault, @@ -19,6 +21,7 @@ import { sansInput, useSettings, } from "@/context/settings" +import { decode64 } from "@/utils/base64" import { playSoundById, SOUND_OPTIONS } from "@/utils/sound" import { Link } from "./link" import { SettingsList } from "./settings-list" @@ -64,7 +67,9 @@ const playDemoSound = (id: string | undefined) => { export const SettingsGeneral: Component = () => { const theme = useTheme() const language = useLanguage() + const permission = usePermission() const platform = usePlatform() + const params = useParams() const settings = useSettings() onMount(() => { @@ -76,6 +81,31 @@ export const SettingsGeneral: Component = () => { }) const linux = createMemo(() => platform.platform === "desktop" && platform.os === "linux") + const dir = createMemo(() => decode64(params.dir)) + const accepting = createMemo(() => { + const value = dir() + if (!value) return false + if (!params.id) return permission.isAutoAcceptingDirectory(value) + return permission.isAutoAccepting(params.id, value) + }) + + const toggleAccept = (checked: boolean) => { + const value = dir() + if (!value) return + + if (!params.id) { + if (permission.isAutoAcceptingDirectory(value) === checked) return + permission.toggleAutoAcceptDirectory(value) + return + } + + if (checked) { + permission.enableAutoAccept(params.id, value) + return + } + + permission.disableAutoAccept(params.id, value) + } const check = () => { if (!platform.checkUpdate) return @@ -201,6 +231,15 @@ export const SettingsGeneral: Component = () => { /> + +
+ +
+
+ Date: Tue, 7 Apr 2026 10:08:57 -0400 Subject: [PATCH 123/616] go: support coupon --- infra/console.ts | 8 +++++ .../console/app/src/routes/stripe/webhook.ts | 33 +++++-------------- packages/console/core/src/billing.ts | 2 +- packages/console/core/src/lite.ts | 7 +++- packages/console/core/sst-env.d.ts | 5 +++ packages/console/function/sst-env.d.ts | 5 +++ packages/console/resource/sst-env.d.ts | 5 +++ packages/enterprise/sst-env.d.ts | 5 +++ packages/function/sst-env.d.ts | 5 +++ sst-env.d.ts | 5 +++ 10 files changed, 53 insertions(+), 27 deletions(-) diff --git a/infra/console.ts b/infra/console.ts index 22652f2daa50..8925f37d5ab7 100644 --- a/infra/console.ts +++ b/infra/console.ts @@ -109,6 +109,12 @@ const zenLiteCouponFirstMonth50 = new stripe.Coupon("ZenLiteCouponFirstMonth50", appliesToProducts: [zenLiteProduct.id], duration: "once", }) +const zenLiteCouponFirstMonth100 = new stripe.Coupon("ZenLiteCouponFirstMonth100", { + name: "First month 100% off", + percentOff: 100, + appliesToProducts: [zenLiteProduct.id], + duration: "once", +}) const zenLitePrice = new stripe.Price("ZenLitePrice", { product: zenLiteProduct.id, currency: "usd", @@ -124,6 +130,7 @@ const ZEN_LITE_PRICE = new sst.Linkable("ZEN_LITE_PRICE", { price: zenLitePrice.id, priceInr: 92900, firstMonth50Coupon: zenLiteCouponFirstMonth50.id, + firstMonth100Coupon: zenLiteCouponFirstMonth100.id, }, }) @@ -229,6 +236,7 @@ new sst.cloudflare.x.SolidStart("Console", { SALESFORCE_INSTANCE_URL, ZEN_BLACK_PRICE, ZEN_LITE_PRICE, + new sst.Secret("ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES"), new sst.Secret("ZEN_LIMITS"), new sst.Secret("ZEN_SESSION_SECRET"), ...ZEN_MODELS, diff --git a/packages/console/app/src/routes/stripe/webhook.ts b/packages/console/app/src/routes/stripe/webhook.ts index 47fee05cf015..0d8cf61cfa8d 100644 --- a/packages/console/app/src/routes/stripe/webhook.ts +++ b/packages/console/app/src/routes/stripe/webhook.ts @@ -1,3 +1,4 @@ +import type { Stripe } from "stripe" import { Billing } from "@opencode-ai/console-core/billing.js" import type { APIEvent } from "@solidjs/start/server" import { and, Database, eq, sql } from "@opencode-ai/console-core/drizzle/index.js" @@ -111,27 +112,17 @@ export async function POST(input: APIEvent) { const customerID = body.data.object.customer as string const invoiceID = body.data.object.latest_invoice as string const subscriptionID = body.data.object.id as string + const paymentMethodID = body.data.object.default_payment_method as string if (!workspaceID) throw new Error("Workspace ID not found") if (!userID) throw new Error("User ID not found") if (!customerID) throw new Error("Customer ID not found") if (!invoiceID) throw new Error("Invoice ID not found") if (!subscriptionID) throw new Error("Subscription ID not found") - - // get payment id from invoice - const invoice = await Billing.stripe().invoices.retrieve(invoiceID, { - expand: ["payments"], - }) - const paymentID = invoice.payments?.data[0].payment.payment_intent as string - if (!paymentID) throw new Error("Payment ID not found") + if (!paymentMethodID) throw new Error("Payment method ID not found") // get payment method for the payment intent - const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, { - expand: ["payment_method"], - }) - const paymentMethod = paymentIntent.payment_method - if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded") - + const paymentMethod = await Billing.stripe().paymentMethods.retrieve(paymentMethodID) await Actor.provide("system", { workspaceID }, async () => { // look up current billing const billing = await Billing.get() @@ -200,26 +191,18 @@ export async function POST(input: APIEvent) { const amountInCents = body.data.object.amount_paid const customerID = body.data.object.customer as string const subscriptionID = body.data.object.parent?.subscription_details?.subscription as string + const productID = body.data.object.lines?.data[0].pricing?.price_details?.product as string if (!customerID) throw new Error("Customer ID not found") if (!invoiceID) throw new Error("Invoice ID not found") if (!subscriptionID) throw new Error("Subscription ID not found") // get coupon id from subscription - const subscriptionData = await Billing.stripe().subscriptions.retrieve(subscriptionID, { - expand: ["discounts"], - }) - const couponID = - typeof subscriptionData.discounts[0] === "string" - ? subscriptionData.discounts[0] - : subscriptionData.discounts[0]?.coupon?.id - const productID = subscriptionData.items.data[0].price.product as string - - // get payment id from invoice const invoice = await Billing.stripe().invoices.retrieve(invoiceID, { - expand: ["payments"], + expand: ["discounts", "payments"], }) - const paymentID = invoice.payments?.data[0].payment.payment_intent as string + const paymentID = invoice.payments?.data[0]?.payment.payment_intent as string + const couponID = (invoice.discounts[0] as Stripe.Discount).coupon?.id as string if (!paymentID) { // payment id can be undefined when using coupon if (!couponID) throw new Error("Payment ID not found") diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts index 66b98069851f..9de413e60b5a 100644 --- a/packages/console/core/src/billing.ts +++ b/packages/console/core/src/billing.ts @@ -254,7 +254,7 @@ export namespace Billing { const createSession = () => Billing.stripe().checkout.sessions.create({ mode: "subscription", - discounts: [{ coupon: LiteData.firstMonth50Coupon() }], + discounts: [{ coupon: LiteData.firstMonthCoupon(email!) }], ...(billing.customerID ? { customer: billing.customerID, diff --git a/packages/console/core/src/lite.ts b/packages/console/core/src/lite.ts index 2c4a09f71186..3343192c19ba 100644 --- a/packages/console/core/src/lite.ts +++ b/packages/console/core/src/lite.ts @@ -11,6 +11,11 @@ export namespace LiteData { export const productID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.product) export const priceID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.price) export const priceInr = fn(z.void(), () => Resource.ZEN_LITE_PRICE.priceInr) - export const firstMonth50Coupon = fn(z.void(), () => Resource.ZEN_LITE_PRICE.firstMonth50Coupon) + export const firstMonthCoupon = fn(z.string(), (email) => { + const invitees = Resource.ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES.value.split(",") + return invitees.includes(email) + ? Resource.ZEN_LITE_PRICE.firstMonth100Coupon + : Resource.ZEN_LITE_PRICE.firstMonth50Coupon + }) export const planName = fn(z.void(), () => "lite") } diff --git a/packages/console/core/sst-env.d.ts b/packages/console/core/sst-env.d.ts index 6b842639add6..b77ee3c5bf57 100644 --- a/packages/console/core/sst-env.d.ts +++ b/packages/console/core/sst-env.d.ts @@ -142,7 +142,12 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": { + "type": "sst.sst.Secret" + "value": string + } "ZEN_LITE_PRICE": { + "firstMonth100Coupon": string "firstMonth50Coupon": string "price": string "priceInr": number diff --git a/packages/console/function/sst-env.d.ts b/packages/console/function/sst-env.d.ts index 6b842639add6..b77ee3c5bf57 100644 --- a/packages/console/function/sst-env.d.ts +++ b/packages/console/function/sst-env.d.ts @@ -142,7 +142,12 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": { + "type": "sst.sst.Secret" + "value": string + } "ZEN_LITE_PRICE": { + "firstMonth100Coupon": string "firstMonth50Coupon": string "price": string "priceInr": number diff --git a/packages/console/resource/sst-env.d.ts b/packages/console/resource/sst-env.d.ts index 6b842639add6..b77ee3c5bf57 100644 --- a/packages/console/resource/sst-env.d.ts +++ b/packages/console/resource/sst-env.d.ts @@ -142,7 +142,12 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": { + "type": "sst.sst.Secret" + "value": string + } "ZEN_LITE_PRICE": { + "firstMonth100Coupon": string "firstMonth50Coupon": string "price": string "priceInr": number diff --git a/packages/enterprise/sst-env.d.ts b/packages/enterprise/sst-env.d.ts index 6b842639add6..b77ee3c5bf57 100644 --- a/packages/enterprise/sst-env.d.ts +++ b/packages/enterprise/sst-env.d.ts @@ -142,7 +142,12 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": { + "type": "sst.sst.Secret" + "value": string + } "ZEN_LITE_PRICE": { + "firstMonth100Coupon": string "firstMonth50Coupon": string "price": string "priceInr": number diff --git a/packages/function/sst-env.d.ts b/packages/function/sst-env.d.ts index 6b842639add6..b77ee3c5bf57 100644 --- a/packages/function/sst-env.d.ts +++ b/packages/function/sst-env.d.ts @@ -142,7 +142,12 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": { + "type": "sst.sst.Secret" + "value": string + } "ZEN_LITE_PRICE": { + "firstMonth100Coupon": string "firstMonth50Coupon": string "price": string "priceInr": number diff --git a/sst-env.d.ts b/sst-env.d.ts index c9e567997bf9..2a40a9f3c93f 100644 --- a/sst-env.d.ts +++ b/sst-env.d.ts @@ -168,7 +168,12 @@ declare module "sst" { "type": "sst.sst.Secret" "value": string } + "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": { + "type": "sst.sst.Secret" + "value": string + } "ZEN_LITE_PRICE": { + "firstMonth100Coupon": string "firstMonth50Coupon": string "price": string "priceInr": number From 5048aa4cfd5eeb5f7d70863ca9731172756f620a Mon Sep 17 00:00:00 2001 From: Dax Date: Tue, 7 Apr 2026 10:12:53 -0400 Subject: [PATCH 124/616] fix(opencode): keep user message variants scoped to model (#21332) --- .../app/src/components/prompt-input/submit.test.ts | 5 ++--- packages/app/src/components/prompt-input/submit.ts | 3 +-- packages/app/src/context/local.tsx | 6 +++--- packages/app/src/context/sync.tsx | 3 +-- .../src/pages/session/session-model-helpers.test.ts | 9 +++++---- .../src/cli/cmd/tui/component/prompt/index.tsx | 10 ++++++---- packages/opencode/src/session/compaction.ts | 3 +-- packages/opencode/src/session/llm.ts | 4 +++- packages/opencode/src/session/message-v2.ts | 2 +- packages/opencode/src/session/prompt.ts | 13 ++++++++----- packages/opencode/test/session/llm.test.ts | 6 ++---- packages/opencode/test/session/prompt.test.ts | 12 ++++++++---- packages/sdk/js/src/v2/gen/types.gen.ts | 2 +- 13 files changed, 42 insertions(+), 36 deletions(-) diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts index b0166c43a80f..03bece2e311a 100644 --- a/packages/app/src/components/prompt-input/submit.test.ts +++ b/packages/app/src/components/prompt-input/submit.test.ts @@ -146,7 +146,7 @@ beforeAll(async () => { add: (value: { directory?: string sessionID?: string - message: { agent: string; model: { providerID: string; modelID: string }; variant?: string } + message: { agent: string; model: { providerID: string; modelID: string; variant?: string } } }) => { optimistic.push(value) optimisticSeeded.push( @@ -310,8 +310,7 @@ describe("prompt submit worktree selection", () => { expect(optimistic[0]).toMatchObject({ message: { agent: "agent", - model: { providerID: "provider", modelID: "model" }, - variant: "high", + model: { providerID: "provider", modelID: "model", variant: "high" }, }, }) }) diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index 06b6c1e35108..2a3a3d0e9917 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -121,8 +121,7 @@ export async function sendFollowupDraft(input: FollowupSendInput) { role: "user", time: { created: Date.now() }, agent: input.draft.agent, - model: input.draft.model, - variant: input.draft.variant, + model: { ...input.draft.model, variant: input.draft.variant }, } const add = () => diff --git a/packages/app/src/context/local.tsx b/packages/app/src/context/local.tsx index 84a613c0d285..1633607de4b0 100644 --- a/packages/app/src/context/local.tsx +++ b/packages/app/src/context/local.tsx @@ -11,7 +11,7 @@ import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } fro import { useSDK } from "./sdk" import { useSync } from "./sync" -export type ModelKey = { providerID: string; modelID: string } +export type ModelKey = { providerID: string; modelID: string; variant?: string } type State = { agent?: string @@ -373,7 +373,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ handoff.set(handoffKey(dir, session), next) setStore("draft", undefined) }, - restore(msg: { sessionID: string; agent: string; model: ModelKey; variant?: string }) { + restore(msg: { sessionID: string; agent: string; model: ModelKey }) { const session = id() if (!session) return if (msg.sessionID !== session) return @@ -383,7 +383,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ setSaved("session", session, { agent: msg.agent, model: msg.model, - variant: msg.variant ?? null, + variant: msg.model.variant ?? null, }) }, }, diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx index bbf4fc5ec46d..b023e8ddc177 100644 --- a/packages/app/src/context/sync.tsx +++ b/packages/app/src/context/sync.tsx @@ -416,8 +416,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ role: "user", time: { created: Date.now() }, agent: input.agent, - model: input.model, - variant: input.variant, + model: { ...input.model, variant: input.variant }, } const [, setStore] = target() setOptimistic(sdk.directory, input.sessionID, { message, parts: input.parts }) diff --git a/packages/app/src/pages/session/session-model-helpers.test.ts b/packages/app/src/pages/session/session-model-helpers.test.ts index 319db805d25c..2ab293b8fb3a 100644 --- a/packages/app/src/pages/session/session-model-helpers.test.ts +++ b/packages/app/src/pages/session/session-model-helpers.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test" import type { UserMessage } from "@opencode-ai/sdk/v2" import { resetSessionModel, syncSessionModel } from "./session-model-helpers" -const message = (input?: Partial>) => +const message = (input?: { agent?: string; model?: UserMessage["model"] }) => ({ id: "msg", sessionID: "session", @@ -10,7 +10,6 @@ const message = (input?: Partial { @@ -26,10 +25,12 @@ describe("syncSessionModel", () => { reset() {}, }, }, - message({ variant: "high" }), + message({ model: { providerID: "anthropic", modelID: "claude-sonnet-4", variant: "high" } }), ) - expect(calls).toEqual([message({ variant: "high" })]) + expect(calls).toEqual([ + message({ model: { providerID: "anthropic", modelID: "claude-sonnet-4", variant: "high" } }), + ]) }) }) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 2fef184f5799..045d730c9ef4 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -23,7 +23,7 @@ import { useRenderer, type JSX } from "@opentui/solid" import { Editor } from "@tui/util/editor" import { useExit } from "../../context/exit" import { Clipboard } from "../../util/clipboard" -import type { AssistantMessage, FilePart } from "@opencode-ai/sdk/v2" +import type { AssistantMessage, FilePart, UserMessage } from "@opencode-ai/sdk/v2" import { TuiEvent } from "../../event" import { iife } from "@/util/iife" import { Locale } from "@/util/locale" @@ -145,7 +145,7 @@ export function Prompt(props: PromptProps) { if (!props.sessionID) return undefined const messages = sync.data.message[props.sessionID] if (!messages) return undefined - return messages.findLast((m) => m.role === "user") + return messages.findLast((m): m is UserMessage => m.role === "user") }) const usage = createMemo(() => { @@ -209,8 +209,10 @@ export function Prompt(props: PromptProps) { const isPrimaryAgent = local.agent.list().some((x) => x.name === msg.agent) if (msg.agent && isPrimaryAgent) { local.agent.set(msg.agent) - if (msg.model) local.model.set(msg.model) - if (msg.variant) local.model.variant.set(msg.variant) + if (msg.model) { + local.model.set(msg.model) + local.model.variant.set(msg.model.variant) + } } } }) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 3158393f1145..bbdce9fd7472 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -228,7 +228,7 @@ When constructing the summary, try to stick to this template: sessionID: input.sessionID, mode: "compaction", agent: "compaction", - variant: userMessage.variant, + variant: userMessage.model.variant, summary: true, path: { cwd: ctx.directory, @@ -295,7 +295,6 @@ When constructing the summary, try to stick to this template: format: original.format, tools: original.tools, system: original.system, - variant: original.variant, }) for (const part of replay.parts) { if (part.type === "compaction") continue diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 7f60c02b1d9f..c9a62c8645e0 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -127,7 +127,9 @@ export namespace LLM { } const variant = - !input.small && input.model.variants && input.user.variant ? input.model.variants[input.user.variant] : {} + !input.small && input.model.variants && input.user.model.variant + ? input.model.variants[input.user.model.variant] + : {} const base = input.small ? ProviderTransform.smallOptions(input.model) : ProviderTransform.options({ diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index eb39519854cb..e8aab62d8423 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -371,10 +371,10 @@ export namespace MessageV2 { model: z.object({ providerID: ProviderID.zod, modelID: ModelID.zod, + variant: z.string().optional(), }), system: z.string().optional(), tools: z.record(z.string(), z.boolean()).optional(), - variant: z.string().optional(), }).meta({ ref: "UserMessage", }) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 24996c8d4b29..b91dfded5e6b 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -569,7 +569,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the sessionID, mode: task.agent, agent: task.agent, - variant: lastUser.variant, + variant: lastUser.model.variant, path: { cwd: ctx.directory, root: ctx.worktree }, cost: 0, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, @@ -967,17 +967,20 @@ NOTE: At any point in time through this workflow you should feel free to ask the : undefined const variant = input.variant ?? (ag.variant && full?.variants?.[ag.variant] ? ag.variant : undefined) - const info: MessageV2.Info = { + const info: MessageV2.User = { id: input.messageID ?? MessageID.ascending(), role: "user", sessionID: input.sessionID, time: { created: Date.now() }, tools: input.tools, agent: ag.name, - model, + model: { + providerID: model.providerID, + modelID: model.modelID, + variant, + }, system: input.system, format: input.format, - variant, } yield* Effect.addFinalizer(() => @@ -1436,7 +1439,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the role: "assistant", mode: agent.name, agent: agent.name, - variant: lastUser.variant, + variant: lastUser.model.variant, path: { cwd: ctx.directory, root: ctx.worktree }, cost: 0, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index 946797da50e2..1fa2e61eb241 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -342,8 +342,7 @@ describe("session.llm.stream", () => { role: "user", time: { created: Date.now() }, agent: agent.name, - model: { providerID: ProviderID.make(providerID), modelID: resolved.id }, - variant: "high", + model: { providerID: ProviderID.make(providerID), modelID: resolved.id, variant: "high" }, } satisfies MessageV2.User const stream = await LLM.stream({ @@ -716,8 +715,7 @@ describe("session.llm.stream", () => { role: "user", time: { created: Date.now() }, agent: agent.name, - model: { providerID: ProviderID.make("openai"), modelID: resolved.id }, - variant: "high", + model: { providerID: ProviderID.make("openai"), modelID: resolved.id, variant: "high" }, } satisfies MessageV2.User const stream = await LLM.stream({ diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 51d2e11941ae..bf7b99ef2e23 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -410,7 +410,7 @@ describe("session.prompt agent variant", () => { parts: [{ type: "text", text: "hello" }], }) if (other.info.role !== "user") throw new Error("expected user message") - expect(other.info.variant).toBeUndefined() + expect(other.info.model.variant).toBeUndefined() const match = await SessionPrompt.prompt({ sessionID: session.id, @@ -419,8 +419,12 @@ describe("session.prompt agent variant", () => { parts: [{ type: "text", text: "hello again" }], }) if (match.info.role !== "user") throw new Error("expected user message") - expect(match.info.model).toEqual({ providerID: ProviderID.make("openai"), modelID: ModelID.make("gpt-5.2") }) - expect(match.info.variant).toBe("xhigh") + expect(match.info.model).toEqual({ + providerID: ProviderID.make("openai"), + modelID: ModelID.make("gpt-5.2"), + variant: "xhigh", + }) + expect(match.info.model.variant).toBe("xhigh") const override = await SessionPrompt.prompt({ sessionID: session.id, @@ -430,7 +434,7 @@ describe("session.prompt agent variant", () => { parts: [{ type: "text", text: "hello third" }], }) if (override.info.role !== "user") throw new Error("expected user message") - expect(override.info.variant).toBe("high") + expect(override.info.model.variant).toBe("high") await Session.remove(session.id) }, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 548ab8363e2c..fc1616c4fd6f 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -548,12 +548,12 @@ export type UserMessage = { model: { providerID: string modelID: string + variant?: string } system?: string tools?: { [key: string]: boolean } - variant?: string } export type AssistantMessage = { From 7af21d8d3242e8eef6cb98d171938a3de7f317a5 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 7 Apr 2026 14:14:07 +0000 Subject: [PATCH 125/616] chore: generate --- packages/sdk/openapi.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index e21c48e89a3a..5007e78e8b73 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -8717,6 +8717,9 @@ }, "modelID": { "type": "string" + }, + "variant": { + "type": "string" } }, "required": ["providerID", "modelID"] @@ -8732,9 +8735,6 @@ "additionalProperties": { "type": "boolean" } - }, - "variant": { - "type": "string" } }, "required": ["id", "sessionID", "role", "time", "agent", "model"] From d084c5283814b46ebb26c5ab431567c71e8a9654 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 7 Apr 2026 11:02:37 -0500 Subject: [PATCH 126/616] chore: update web stats --- packages/console/app/src/config.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/console/app/src/config.ts b/packages/console/app/src/config.ts index 19e331c39aac..6f11bfa02876 100644 --- a/packages/console/app/src/config.ts +++ b/packages/console/app/src/config.ts @@ -9,8 +9,8 @@ export const config = { github: { repoUrl: "https://github.com/anomalyco/opencode", starsFormatted: { - compact: "120K", - full: "120,000", + compact: "140K", + full: "140,000", }, }, @@ -22,8 +22,8 @@ export const config = { // Static stats (used on landing page) stats: { - contributors: "800", - commits: "10,000", - monthlyUsers: "5M", + contributors: "850", + commits: "11,000", + monthlyUsers: "6.5M", }, } as const From 238c8cd46cec350b5f7389006bc2543e1c95d7f3 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 7 Apr 2026 11:06:23 -0500 Subject: [PATCH 127/616] feat(app): better subagent experience (#20708) --- .../session/session-child-navigation.spec.ts | 29 +- packages/app/src/i18n/en.ts | 2 + packages/app/src/index.css | 40 ++ packages/app/src/pages/layout.tsx | 17 - packages/app/src/pages/layout/helpers.test.ts | 14 + packages/app/src/pages/layout/helpers.ts | 21 +- .../app/src/pages/layout/sidebar-items.tsx | 296 ++++--------- .../app/src/pages/layout/sidebar-project.tsx | 27 +- .../src/pages/layout/sidebar-workspace.tsx | 26 +- packages/app/src/pages/session.tsx | 14 +- .../composer/session-composer-region.tsx | 61 ++- .../src/pages/session/message-timeline.tsx | 419 +++++++++++------- packages/app/src/utils/agent.ts | 23 +- packages/ui/src/components/basic-tool.css | 92 ++++ packages/ui/src/components/basic-tool.tsx | 142 +++--- packages/ui/src/components/collapsible.css | 5 + packages/ui/src/components/message-part.tsx | 150 +++++-- packages/ui/src/context/data.tsx | 4 + 18 files changed, 831 insertions(+), 551 deletions(-) diff --git a/packages/app/e2e/session/session-child-navigation.spec.ts b/packages/app/e2e/session/session-child-navigation.spec.ts index 34a1a9e2e745..c9fad1af8532 100644 --- a/packages/app/e2e/session/session-child-navigation.spec.ts +++ b/packages/app/e2e/session/session-child-navigation.spec.ts @@ -1,7 +1,6 @@ import { seedSessionTask, withSession } from "../actions" import { test, expect } from "../fixtures" import { inputMatch } from "../prompt/mock" -import { promptSelector } from "../selectors" test("task tool child-session link does not trigger stale show errors", async ({ page, llm, project }) => { test.setTimeout(120_000) @@ -30,15 +29,33 @@ test("task tool child-session link does not trigger stale show errors", async ({ await project.gotoSession(session.id) - const link = page - .locator("a.subagent-link") + const header = page.locator("[data-session-title]") + await expect(header.getByRole("button", { name: "More options" })).toBeVisible({ timeout: 30_000 }) + + const card = page + .locator('[data-component="task-tool-card"]') .filter({ hasText: /open child session/i }) .first() - await expect(link).toBeVisible({ timeout: 30_000 }) - await link.click() + await expect(card).toBeVisible({ timeout: 30_000 }) + await card.click() await expect(page).toHaveURL(new RegExp(`/session/${child.sessionID}(?:[/?#]|$)`), { timeout: 30_000 }) - await expect(page.locator(promptSelector)).toBeVisible({ timeout: 30_000 }) + await expect(header.locator('[data-slot="session-title-parent"]')).toHaveText(session.title) + await expect(header.locator('[data-slot="session-title-child"]')).toHaveText(taskInput.description) + await expect(header.locator('[data-slot="session-title-separator"]')).toHaveText("/") + await expect + .poll( + () => + header.locator('[data-slot="session-title-separator"]').evaluate((el) => ({ + left: getComputedStyle(el).paddingLeft, + right: getComputedStyle(el).paddingRight, + })), + { timeout: 30_000 }, + ) + .toEqual({ left: "8px", right: "8px" }) + await expect(header.getByRole("button", { name: "More options" })).toHaveCount(0) + await expect(page.getByText("Subagent sessions cannot be prompted.")).toBeVisible({ timeout: 30_000 }) + await expect(page.getByRole("button", { name: "Back to main session." })).toBeVisible({ timeout: 30_000 }) await expect.poll(() => errs, { timeout: 5_000 }).toEqual([]) }) } finally { diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index ace0efeb8714..c6bcc37b116f 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -238,6 +238,8 @@ export const dict = { "prompt.mode.shell": "Shell", "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc to exit", + "session.child.promptDisabled": "Subagent sessions cannot be prompted.", + "session.child.backToParent": "Back to main session.", "prompt.example.1": "Fix a TODO in the codebase", "prompt.example.2": "What is the tech stack of this project?", diff --git a/packages/app/src/index.css b/packages/app/src/index.css index 9e231e2d2858..629ac80a8698 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -1,6 +1,46 @@ @import "@opencode-ai/ui/styles/tailwind"; @layer components { + @keyframes session-progress-whip { + 0% { + clip-path: inset(0 100% 0 0 round 999px); + animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1); + } + + 48% { + clip-path: inset(0 0 0 0 round 999px); + animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1); + } + + 100% { + clip-path: inset(0 0 0 100% round 999px); + } + } + + [data-component="session-progress"] { + position: absolute; + inset: 0 0 auto; + height: 2px; + overflow: hidden; + pointer-events: none; + opacity: 1; + transition: opacity 220ms ease-out; + } + + [data-component="session-progress"][data-state="hiding"] { + opacity: 0; + } + + [data-component="session-progress-bar"] { + width: 100%; + height: 100%; + border-radius: 999px; + background: var(--session-progress-color); + clip-path: inset(0 100% 0 0 round 999px); + animation: session-progress-whip var(--session-progress-ms, 1800ms) infinite; + will-change: clip-path; + } + [data-component="getting-started"] { container-type: inline-size; container-name: getting-started; diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 79b9abd33284..f402f4bc04df 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -150,7 +150,6 @@ export default function Layout(props: ParentProps) { const [state, setState] = createStore({ autoselect: !initialDirectory, busyWorkspaces: {} as Record, - hoverSession: undefined as string | undefined, hoverProject: undefined as string | undefined, scrollSessionKey: undefined as string | undefined, nav: undefined as HTMLElement | undefined, @@ -194,7 +193,6 @@ export default function Layout(props: ParentProps) { onActivate: (directory) => { globalSync.child(directory) setState("hoverProject", directory) - setState("hoverSession", undefined) }, }) @@ -231,7 +229,6 @@ export default function Layout(props: ParentProps) { aim.reset() } const clearHoverProjectSoon = () => queueMicrotask(() => setHoverProject(undefined)) - const setHoverSession = (id: string | undefined) => setState("hoverSession", id) const disarm = () => { if (navLeave.current === undefined) return @@ -241,7 +238,6 @@ export default function Layout(props: ParentProps) { const reset = () => { disarm() - setState("hoverSession", undefined) setHoverProject(undefined) } @@ -252,7 +248,6 @@ export default function Layout(props: ParentProps) { navLeave.current = window.setTimeout(() => { navLeave.current = undefined setHoverProject(undefined) - setState("hoverSession", undefined) }, 300) } @@ -1972,9 +1967,6 @@ export default function Layout(props: ParentProps) { navList: currentSessions, sidebarExpanded, sidebarHovering, - nav: () => state.nav, - hoverSession: () => state.hoverSession, - setHoverSession, clearHoverProjectSoon, prefetchSession, archiveSession, @@ -2003,7 +1995,6 @@ export default function Layout(props: ParentProps) { sidebarOpened: () => layout.sidebar.opened(), sidebarHovering, hoverProject: () => state.hoverProject, - nav: () => state.nav, onProjectMouseEnter: (worktree, event) => aim.enter(worktree, event), onProjectMouseLeave: (worktree) => aim.leave(worktree), onProjectFocus: (worktree) => aim.activate(worktree), @@ -2022,15 +2013,10 @@ export default function Layout(props: ParentProps) { sessionProps: { navList: currentSessions, sidebarExpanded, - sidebarHovering, - nav: () => state.nav, - hoverSession: () => state.hoverSession, - setHoverSession, clearHoverProjectSoon, prefetchSession, archiveSession, }, - setHoverSession, } const SidebarPanel = (panelProps: { @@ -2041,7 +2027,6 @@ export default function Layout(props: ParentProps) { const project = panelProps.project const merged = createMemo(() => panelProps.mobile || (panelProps.merged ?? layout.sidebar.opened())) const hover = createMemo(() => !panelProps.mobile && panelProps.merged === false && !layout.sidebar.opened()) - const popover = createMemo(() => !!panelProps.mobile || panelProps.merged === false || layout.sidebar.opened()) const empty = createMemo(() => !params.dir && layout.projects.list().length === 0) const projectName = createMemo(() => { const item = project() @@ -2243,7 +2228,6 @@ export default function Layout(props: ParentProps) { project={project()!} sortNow={sortNow} mobile={panelProps.mobile} - popover={popover()} />
@@ -2288,7 +2272,6 @@ export default function Layout(props: ParentProps) { project={project()!} sortNow={sortNow} mobile={panelProps.mobile} - popover={popover()} /> )} diff --git a/packages/app/src/pages/layout/helpers.test.ts b/packages/app/src/pages/layout/helpers.test.ts index 1fe52d47a0a6..988332ab7ce1 100644 --- a/packages/app/src/pages/layout/helpers.test.ts +++ b/packages/app/src/pages/layout/helpers.test.ts @@ -8,6 +8,7 @@ import { } from "./deep-links" import { type Session } from "@opencode-ai/sdk/v2/client" import { + childSessionOnPath, displayName, effectiveWorkspaceOrder, errorMessage, @@ -198,6 +199,19 @@ describe("layout workspace helpers", () => { expect(result?.id).toBe("root") }) + test("finds the direct child on the active session path", () => { + const list = [ + session({ id: "root", directory: "/workspace" }), + session({ id: "child", directory: "/workspace", parentID: "root" }), + session({ id: "leaf", directory: "/workspace", parentID: "child" }), + ] + + expect(childSessionOnPath(list, "root", "leaf")?.id).toBe("child") + expect(childSessionOnPath(list, "child", "leaf")?.id).toBe("leaf") + expect(childSessionOnPath(list, "root", "root")).toBeUndefined() + expect(childSessionOnPath(list, "root", "other")).toBeUndefined() + }) + test("formats fallback project display name", () => { expect(displayName({ worktree: "/tmp/app" })).toBe("app") expect(displayName({ worktree: "/tmp/app", name: "My App" })).toBe("My App") diff --git a/packages/app/src/pages/layout/helpers.ts b/packages/app/src/pages/layout/helpers.ts index 226098c1cd66..48158debba1d 100644 --- a/packages/app/src/pages/layout/helpers.ts +++ b/packages/app/src/pages/layout/helpers.ts @@ -46,18 +46,17 @@ export function hasProjectPermissions( return Object.values(request ?? {}).some((list) => list?.some(include)) } -export const childMapByParent = (sessions: Session[] | undefined) => { - const map = new Map() - for (const session of sessions ?? []) { - if (!session.parentID) continue - const existing = map.get(session.parentID) - if (existing) { - existing.push(session.id) - continue - } - map.set(session.parentID, [session.id]) +export const childSessionOnPath = (sessions: Session[] | undefined, rootID: string, activeID?: string) => { + if (!activeID || activeID === rootID) return + const map = new Map((sessions ?? []).map((session) => [session.id, session])) + let id = activeID + + while (id) { + const session = map.get(id) + if (!session?.parentID) return + if (session.parentID === rootID) return session + id = session.parentID } - return map } export const displayName = (project: { name?: string; worktree: string }) => diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index 058bb5a0dbed..e56accfc8353 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -1,15 +1,12 @@ -import type { Message, Session, TextPart, UserMessage } from "@opencode-ai/sdk/v2/client" +import type { Session } from "@opencode-ai/sdk/v2/client" import { Avatar } from "@opencode-ai/ui/avatar" -import { HoverCard } from "@opencode-ai/ui/hover-card" import { Icon } from "@opencode-ai/ui/icon" import { IconButton } from "@opencode-ai/ui/icon-button" -import { MessageNav } from "@opencode-ai/ui/message-nav" import { Spinner } from "@opencode-ai/ui/spinner" import { Tooltip } from "@opencode-ai/ui/tooltip" -import { base64Encode } from "@opencode-ai/util/encode" import { getFilename } from "@opencode-ai/util/path" -import { A, useNavigate, useParams } from "@solidjs/router" -import { type Accessor, createMemo, For, type JSX, Match, onCleanup, Show, Switch } from "solid-js" +import { A, useParams } from "@solidjs/router" +import { type Accessor, createMemo, For, type JSX, Match, Show, Switch } from "solid-js" import { useGlobalSync } from "@/context/global-sync" import { useLanguage } from "@/context/language" import { getAvatarColors, type LocalProject, useLayout } from "@/context/layout" @@ -18,7 +15,7 @@ import { usePermission } from "@/context/permission" import { messageAgentColor } from "@/utils/agent" import { sessionTitle } from "@/utils/session-title" import { sessionPermissionRequest } from "../session/composer/session-request-tree" -import { hasProjectPermissions } from "./helpers" +import { childSessionOnPath, hasProjectPermissions } from "./helpers" const OPENCODE_PROJECT_ID = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750" @@ -39,6 +36,7 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti ) const notify = createMemo(() => props.notify && (hasPermissions() || unseenCount() > 0)) const name = createMemo(() => props.project.name || getFilename(props.project.worktree)) + return (
@@ -73,13 +71,10 @@ export type SessionItemProps = { slug: string mobile?: boolean dense?: boolean - popover?: boolean - children: Map + showTooltip?: boolean + showChild?: boolean + level?: number sidebarExpanded: Accessor - sidebarHovering: Accessor - nav: Accessor - hoverSession: Accessor - setHoverSession: (id: string | undefined) => void clearHoverProjectSoon: () => void prefetchSession: (session: Session, priority?: "high" | "low") => void archiveSession: (session: Session) => Promise @@ -95,116 +90,52 @@ const SessionRow = (props: { hasPermissions: Accessor hasError: Accessor unseenCount: Accessor - setHoverSession: (id: string | undefined) => void clearHoverProjectSoon: () => void sidebarOpened: Accessor - warmHover: () => void warmPress: () => void warmFocus: () => void - cancelHoverPrefetch: () => void -}) => { +}): JSX.Element => { const title = () => sessionTitle(props.session.title) return ( { - props.setHoverSession(undefined) if (props.sidebarOpened()) return props.clearHoverProjectSoon() }} > -
- }> - - - - -
- - -
- - 0}> -
- - -
- {title()} -
- ) -} - -const SessionHoverPreview = (props: { - mobile?: boolean - nav: Accessor - hoverSession: Accessor - session: Session - sidebarHovering: Accessor - hoverReady: Accessor - hoverMessages: Accessor - language: ReturnType - isActive: Accessor - slug: string - setHoverSession: (id: string | undefined) => void - messageLabel: (message: Message) => string | undefined - onMessageSelect: (message: Message) => void - trigger: JSX.Element -}): JSX.Element => { - let ref: HTMLDivElement | undefined - - return ( - - {props.trigger} -
- } - open={props.hoverSession() === props.session.id} - onOpenChange={(open) => { - if (!open) { - props.setHoverSession(undefined) - return - } - if (!ref?.matches(":hover")) return - props.setHoverSession(props.session.id) - }} - > - {props.language.t("session.messages.loading")}
} - > -
- + 0}> +
+ + + + + +
+ + +
+ + 0}> +
+ +
- + {title()} + ) } export const SessionItem = (props: SessionItemProps): JSX.Element => { const params = useParams() - const navigate = useNavigate() const layout = useLayout() const language = useLanguage() const notification = useNotification() @@ -234,18 +165,13 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { ) }) - const tint = createMemo(() => { - return messageAgentColor(sessionStore.message[props.session.id], sessionStore.agent) + const tint = createMemo(() => messageAgentColor(sessionStore.message[props.session.id], sessionStore.agent)) + const tooltip = createMemo(() => props.showTooltip ?? (props.mobile || !props.sidebarExpanded())) + const currentChild = createMemo(() => { + if (!props.showChild) return + return childSessionOnPath(sessionStore.session, props.session.id, params.id) }) - const hoverMessages = createMemo(() => - sessionStore.message[props.session.id]?.filter((message): message is UserMessage => message.role === "user"), - ) - const hoverReady = createMemo(() => hoverMessages() !== undefined) - const hoverAllowed = createMemo(() => !props.mobile && props.sidebarExpanded()) - const hoverEnabled = createMemo(() => (props.popover ?? true) && hoverAllowed()) - const isActive = createMemo(() => props.session.id === params.id) - const warm = (span: number, priority: "high" | "low") => { const nav = props.navList?.() const list = nav?.some((item) => item.id === props.session.id && item.directory === props.session.directory) @@ -266,30 +192,6 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => { } } - const hoverPrefetch = { - current: undefined as ReturnType | undefined, - } - const cancelHoverPrefetch = () => { - if (hoverPrefetch.current === undefined) return - clearTimeout(hoverPrefetch.current) - hoverPrefetch.current = undefined - } - const scheduleHoverPrefetch = () => { - warm(1, "high") - if (hoverPrefetch.current !== undefined) return - hoverPrefetch.current = setTimeout(() => { - hoverPrefetch.current = undefined - warm(2, "low") - }, 80) - } - - onCleanup(cancelHoverPrefetch) - - const messageLabel = (message: Message) => { - const parts = sessionStore.part[message.id] ?? [] - const text = parts.find((part): part is TextPart => part?.type === "text" && !part.synthetic && !part.ignored) - return text?.text - } const item = ( { hasPermissions={hasPermissions} hasError={hasError} unseenCount={unseenCount} - setHoverSession={props.setHoverSession} clearHoverProjectSoon={props.clearHoverProjectSoon} sidebarOpened={layout.sidebar.opened} - warmHover={scheduleHoverPrefetch} warmPress={() => warm(2, "high")} warmFocus={() => warm(2, "high")} - cancelHoverPrefetch={cancelHoverPrefetch} /> ) return ( -
-
-
- - {item} - - } - > - { - if (!isActive()) - layout.pendingMessage.set(`${base64Encode(props.session.directory)}/${props.session.id}`, message.id) + <> +
+
+
+ + {item} + + } + > + {item} + +
- navigate(`${props.slug}/session/${props.session.id}#message-${message.id}`) + +
+ > + + { + event.preventDefault() + event.stopPropagation() + void props.archiveSession(props.session) + }} + /> + +
- -
- - { - event.preventDefault() - event.stopPropagation() - void props.archiveSession(props.session) - }} - /> - -
-
+ + {(child) => ( +
+ +
+ )} +
+ ) } @@ -390,7 +280,6 @@ export const NewSessionItem = (props: { dense?: boolean sidebarExpanded: Accessor clearHoverProjectSoon: () => void - setHoverSession: (id: string | undefined) => void }): JSX.Element => { const layout = useLayout() const language = useLanguage() @@ -400,9 +289,8 @@ export const NewSessionItem = (props: { { - props.setHoverSession(undefined) if (layout.sidebar.opened()) return props.clearHoverProjectSoon() }} diff --git a/packages/app/src/pages/layout/sidebar-project.tsx b/packages/app/src/pages/layout/sidebar-project.tsx index aff0645dd894..7c9ae1aafba6 100644 --- a/packages/app/src/pages/layout/sidebar-project.tsx +++ b/packages/app/src/pages/layout/sidebar-project.tsx @@ -1,4 +1,4 @@ -import { createEffect, createMemo, For, Show, type Accessor, type JSX } from "solid-js" +import { createMemo, For, Show, type Accessor, type JSX } from "solid-js" import { createStore } from "solid-js/store" import { base64Encode } from "@opencode-ai/util/encode" import { Button } from "@opencode-ai/ui/button" @@ -11,7 +11,7 @@ import { useGlobalSync } from "@/context/global-sync" import { useLanguage } from "@/context/language" import { useNotification } from "@/context/notification" import { ProjectIcon, SessionItem, type SessionItemProps } from "./sidebar-items" -import { childMapByParent, displayName, sortedRootSessions } from "./helpers" +import { displayName, sortedRootSessions } from "./helpers" export type ProjectSidebarContext = { currentDir: Accessor @@ -19,7 +19,6 @@ export type ProjectSidebarContext = { sidebarOpened: Accessor sidebarHovering: Accessor hoverProject: Accessor - nav: Accessor onProjectMouseEnter: (worktree: string, event: MouseEvent) => void onProjectMouseLeave: (worktree: string) => void onProjectFocus: (worktree: string) => void @@ -32,8 +31,7 @@ export type ProjectSidebarContext = { workspacesEnabled: (project: LocalProject) => boolean workspaceIds: (project: LocalProject) => string[] workspaceLabel: (directory: string, branch?: string, projectId?: string) => string - sessionProps: Omit - setHoverSession: (id: string | undefined) => void + sessionProps: Omit } export const ProjectDragOverlay = (props: { @@ -55,7 +53,6 @@ export const ProjectDragOverlay = (props: { const ProjectTile = (props: { project: LocalProject mobile?: boolean - nav: Accessor sidebarHovering: Accessor selected: Accessor active: Accessor @@ -195,9 +192,7 @@ const ProjectPreviewPanel = (props: { workspaces: Accessor label: (directory: string) => string projectSessions: Accessor> - projectChildren: Accessor> workspaceSessions: (directory: string) => ReturnType - workspaceChildren: (directory: string) => Map ctx: ProjectSidebarContext language: ReturnType }): JSX.Element => ( @@ -218,9 +213,8 @@ const ProjectPreviewPanel = (props: { list={props.projectSessions()} slug={base64Encode(props.project.worktree)} dense + showTooltip mobile={props.mobile} - popover={false} - children={props.projectChildren()} /> )} @@ -229,7 +223,6 @@ const ProjectPreviewPanel = (props: { {(directory) => { const sessions = createMemo(() => props.workspaceSessions(directory)) - const children = createMemo(() => props.workspaceChildren(directory)) return (
@@ -246,9 +239,8 @@ const ProjectPreviewPanel = (props: { list={sessions()} slug={base64Encode(directory)} dense + showTooltip mobile={props.mobile} - popover={false} - children={children()} /> )} @@ -310,20 +302,14 @@ export const SortableProject = (props: { const projectStore = createMemo(() => globalSync.child(props.project.worktree, { bootstrap: false })[0]) const projectSessions = createMemo(() => sortedRootSessions(projectStore(), props.sortNow())) - const projectChildren = createMemo(() => childMapByParent(projectStore().session)) const workspaceSessions = (directory: string) => { const [data] = globalSync.child(directory, { bootstrap: false }) return sortedRootSessions(data, props.sortNow()) } - const workspaceChildren = (directory: string) => { - const [data] = globalSync.child(directory, { bootstrap: false }) - return childMapByParent(data.session) - } const tile = () => ( diff --git a/packages/app/src/pages/layout/sidebar-workspace.tsx b/packages/app/src/pages/layout/sidebar-workspace.tsx index 3bf00ea424d6..68e36ff77aec 100644 --- a/packages/app/src/pages/layout/sidebar-workspace.tsx +++ b/packages/app/src/pages/layout/sidebar-workspace.tsx @@ -17,7 +17,7 @@ import { type LocalProject } from "@/context/layout" import { useGlobalSync } from "@/context/global-sync" import { useLanguage } from "@/context/language" import { NewSessionItem, SessionItem, SessionSkeleton } from "./sidebar-items" -import { childMapByParent, sortedRootSessions, workspaceKey } from "./helpers" +import { sortedRootSessions, workspaceKey } from "./helpers" type InlineEditorComponent = (props: { id: string @@ -35,9 +35,6 @@ export type WorkspaceSidebarContext = { navList: Accessor sidebarExpanded: Accessor sidebarHovering: Accessor - nav: Accessor - hoverSession: Accessor - setHoverSession: (id: string | undefined) => void clearHoverProjectSoon: () => void prefetchSession: (session: Session, priority?: "high" | "low") => void archiveSession: (session: Session) => Promise @@ -152,7 +149,6 @@ const WorkspaceActions = (props: { showResetWorkspaceDialog: WorkspaceSidebarContext["showResetWorkspaceDialog"] showDeleteWorkspaceDialog: WorkspaceSidebarContext["showDeleteWorkspaceDialog"] root: string - setHoverSession: WorkspaceSidebarContext["setHoverSession"] clearHoverProjectSoon: WorkspaceSidebarContext["clearHoverProjectSoon"] navigateToNewSession: () => void }): JSX.Element => ( @@ -226,7 +222,6 @@ const WorkspaceActions = (props: { onClick={(event) => { event.preventDefault() event.stopPropagation() - props.setHoverSession(undefined) props.clearHoverProjectSoon() props.navigateToNewSession() }} @@ -239,12 +234,10 @@ const WorkspaceActions = (props: { const WorkspaceSessionList = (props: { slug: Accessor mobile?: boolean - popover?: boolean ctx: WorkspaceSidebarContext showNew: Accessor loading: Accessor sessions: Accessor - children: Accessor> hasMore: Accessor loadMore: () => Promise language: ReturnType @@ -256,7 +249,6 @@ const WorkspaceSessionList = (props: { mobile={props.mobile} sidebarExpanded={props.ctx.sidebarExpanded} clearHoverProjectSoon={props.ctx.clearHoverProjectSoon} - setHoverSession={props.ctx.setHoverSession} /> @@ -270,13 +262,8 @@ const WorkspaceSessionList = (props: { navList={props.ctx.navList} slug={props.slug()} mobile={props.mobile} - popover={props.popover} - children={props.children()} + showChild sidebarExpanded={props.ctx.sidebarExpanded} - sidebarHovering={props.ctx.sidebarHovering} - nav={props.ctx.nav} - hoverSession={props.ctx.hoverSession} - setHoverSession={props.ctx.setHoverSession} clearHoverProjectSoon={props.ctx.clearHoverProjectSoon} prefetchSession={props.ctx.prefetchSession} archiveSession={props.ctx.archiveSession} @@ -307,7 +294,6 @@ export const SortableWorkspace = (props: { project: LocalProject sortNow: Accessor mobile?: boolean - popover?: boolean }): JSX.Element => { const navigate = useNavigate() const params = useParams() @@ -321,7 +307,6 @@ export const SortableWorkspace = (props: { }) const slug = createMemo(() => base64Encode(props.directory)) const sessions = createMemo(() => sortedRootSessions(workspaceStore, props.sortNow())) - const children = createMemo(() => childMapByParent(workspaceStore.session)) const local = createMemo(() => props.directory === props.project.worktree) const active = createMemo(() => workspaceKey(props.ctx.currentDir()) === workspaceKey(props.directory)) const workspaceValue = createMemo(() => { @@ -428,7 +413,6 @@ export const SortableWorkspace = (props: { showResetWorkspaceDialog={props.ctx.showResetWorkspaceDialog} showDeleteWorkspaceDialog={props.ctx.showDeleteWorkspaceDialog} root={props.project.worktree} - setHoverSession={props.ctx.setHoverSession} clearHoverProjectSoon={props.ctx.clearHoverProjectSoon} navigateToNewSession={() => navigate(`/${slug()}/session`)} /> @@ -440,12 +424,10 @@ export const SortableWorkspace = (props: { mobile?: boolean - popover?: boolean }): JSX.Element => { const globalSync = useGlobalSync() const language = useLanguage() @@ -471,7 +452,6 @@ export const LocalWorkspace = (props: { }) const slug = createMemo(() => base64Encode(props.project.worktree)) const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow())) - const children = createMemo(() => childMapByParent(workspace().store.session)) const booted = createMemo((prev) => prev || workspace().store.status === "complete", false) const count = createMemo(() => sessions()?.length ?? 0) const loading = createMemo(() => !booted() && count() === 0) @@ -489,12 +469,10 @@ export const LocalWorkspace = (props: { false} loading={loading} sessions={sessions} - children={children} hasMore={hasMore} loadMore={loadMore} language={language} diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index a81df9dd2779..0c67647261f7 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -429,6 +429,7 @@ export default function Page() { } const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) + const isChildSession = createMemo(() => !!info()?.parentID) const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : [])) const sessionCount = createMemo(() => Math.max(info()?.summary?.files ?? 0, diffs().length)) const hasSessionReview = createMemo(() => sessionCount() > 0) @@ -1058,7 +1059,7 @@ export default function Page() { } if (event.key.length === 1 && event.key !== "Unidentified" && !(event.ctrlKey || event.metaKey)) { - if (composer.blocked()) return + if (composer.blocked() || isChildSession()) return inputRef?.focus() } } @@ -1127,7 +1128,10 @@ export default function Page() { setFileTreeTab("all") } - const focusInput = () => inputRef?.focus() + const focusInput = () => { + if (isChildSession()) return + inputRef?.focus() + } useSessionCommands({ navigateMessageByOffset, @@ -1658,7 +1662,7 @@ export default function Page() { const queueEnabled = createMemo(() => { const id = params.id if (!id) return false - return settings.general.followup() === "queue" && busy(id) && !composer.blocked() + return settings.general.followup() === "queue" && busy(id) && !composer.blocked() && !isChildSession() }) const followupText = (item: FollowupDraft) => { @@ -1690,6 +1694,7 @@ export default function Page() { const followupDock = createMemo(() => queuedFollowups().map((item) => ({ id: item.id, text: followupText(item) }))) const sendFollowup = (sessionID: string, id: string, opts?: { manual?: boolean }) => { + if (sync.session.get(sessionID)?.parentID) return Promise.resolve() const item = (followup.items[sessionID] ?? []).find((entry) => entry.id === id) if (!item) return Promise.resolve() if (followupBusy(sessionID)) return Promise.resolve() @@ -1820,6 +1825,7 @@ export default function Page() { if (followupBusy(sessionID)) return if (followup.failed[sessionID] === item.id) return if (followup.paused[sessionID]) return + if (isChildSession()) return if (composer.blocked()) return if (busy(sessionID)) return @@ -2001,7 +2007,7 @@ export default function Page() { }} onResponseSubmit={resumeScroll} followup={ - params.id + params.id && !isChildSession() ? { queue: queueEnabled, items: followupDock(), diff --git a/packages/app/src/pages/session/composer/session-composer-region.tsx b/packages/app/src/pages/session/composer/session-composer-region.tsx index 372adef96af6..60447566ed01 100644 --- a/packages/app/src/pages/session/composer/session-composer-region.tsx +++ b/packages/app/src/pages/session/composer/session-composer-region.tsx @@ -1,9 +1,11 @@ import { Show, createEffect, createMemo, onCleanup } from "solid-js" import { createStore } from "solid-js/store" +import { useNavigate } from "@solidjs/router" import { useSpring } from "@opencode-ai/ui/motion-spring" import { PromptInput } from "@/components/prompt-input" import { useLanguage } from "@/context/language" import { usePrompt } from "@/context/prompt" +import { useSync } from "@/context/sync" import { getSessionHandoff, setSessionHandoff } from "@/pages/session/handoff" import { useSessionKey } from "@/pages/session/session-layout" import { SessionPermissionDock } from "@/pages/session/composer/session-permission-dock" @@ -43,11 +45,17 @@ export function SessionComposerRegion(props: { } setPromptDockRef: (el: HTMLDivElement) => void }) { + const navigate = useNavigate() const prompt = usePrompt() const language = useLanguage() const route = useSessionKey() + const sync = useSync() const handoffPrompt = createMemo(() => getSessionHandoff(route.sessionKey())?.prompt) + const info = createMemo(() => (route.params.id ? sync.session.get(route.params.id) : undefined)) + const parentID = createMemo(() => info()?.parentID) + const child = createMemo(() => !!parentID()) + const showComposer = createMemo(() => !props.state.blocked() || child()) const previewPrompt = () => prompt @@ -113,6 +121,12 @@ export function SessionComposerRegion(props: { const lift = createMemo(() => (rolled() ? 18 : 36 * value())) const full = createMemo(() => Math.max(78, store.height)) + const openParent = () => { + const id = parentID() + if (!id) return + navigate(`/${route.params.dir}/session/${id}`) + } + createEffect(() => { const el = store.body if (!el) return @@ -156,7 +170,7 @@ export function SessionComposerRegion(props: { )} - + - + + + + } + > +
+ {language.t("session.child.promptDisabled")} + + + +
+
diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index bc211303a6ac..fe6447c2e8c8 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -21,6 +21,7 @@ import { Popover as KobaltePopover } from "@kobalte/core/popover" import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture" import { SessionContextUsage } from "@/components/session-context-usage" import { useDialog } from "@opencode-ai/ui/context/dialog" +import { createResizeObserver } from "@solid-primitives/resize-observer" import { useLanguage } from "@/context/language" import { useSessionKey } from "@/pages/session/session-layout" import { useGlobalSDK } from "@/context/global-sdk" @@ -68,6 +69,16 @@ const messageComments = (parts: Part[]): MessageComment[] => ] }) +const taskDescription = (part: Part, sessionID: string) => { + if (part.type !== "tool" || part.tool !== "task") return + const metadata = "metadata" in part.state ? part.state.metadata : undefined + if (metadata?.sessionId !== sessionID) return + const value = part.state.input?.description + if (typeof value === "string" && value) return value +} + +const pace = (width: number) => Math.round(Math.max(1200, Math.min(3200, (Math.max(width, 360) * 2000) / 900))) + const boundaryTarget = (root: HTMLElement, target: EventTarget | null) => { const current = target instanceof Element ? target : undefined const nested = current?.closest("[data-scrollable]") @@ -295,6 +306,32 @@ export function MessageTimeline(props: { const shareUrl = createMemo(() => info()?.share?.url) const shareEnabled = createMemo(() => sync.data.config.share !== "disabled") const parentID = createMemo(() => info()?.parentID) + const parent = createMemo(() => { + const id = parentID() + if (!id) return + return sync.session.get(id) + }) + const parentMessages = createMemo(() => { + const id = parentID() + if (!id) return emptyMessages + return sync.data.message[id] ?? emptyMessages + }) + const parentTitle = createMemo(() => sessionTitle(parent()?.title) ?? language.t("command.session.new")) + const childTaskDescription = createMemo(() => { + const id = sessionID() + if (!id) return + return parentMessages() + .flatMap((message) => sync.data.part[message.id] ?? []) + .map((part) => taskDescription(part, id)) + .findLast((value): value is string => !!value) + }) + const childTitle = createMemo(() => { + if (!parentID()) return titleLabel() ?? "" + if (childTaskDescription()) return childTaskDescription() + const value = titleLabel()?.replace(/\s+\(@[^)]+ subagent\)$/, "") + if (value) return value + return language.t("command.session.new") + }) const showHeader = createMemo(() => !!(titleValue() || parentID())) const stageCfg = { init: 1, batch: 3 } const staging = createTimelineStaging({ @@ -317,8 +354,20 @@ export function MessageTimeline(props: { open: false, dismiss: null as "escape" | "outside" | null, }) + const [bar, setBar] = createStore({ + ms: pace(640), + }) let more: HTMLButtonElement | undefined + let head: HTMLDivElement | undefined + + createResizeObserver( + () => head, + () => { + if (!head || head.clientWidth <= 0) return + setBar("ms", pace(head.clientWidth)) + }, + ) const viewShare = () => { const url = shareUrl() @@ -398,8 +447,20 @@ export function MessageTimeline(props: { ), ) + createEffect( + on( + () => [parentID(), childTaskDescription()] as const, + ([id, description]) => { + if (!id || description) return + if (sync.data.message[id] !== undefined) return + void sync.session.sync(id) + }, + { defer: true }, + ), + ) + const openTitleEditor = () => { - if (!sessionID()) return + if (!sessionID() || parentID()) return setTitle({ editing: true, draft: titleLabel() ?? "" }) requestAnimationFrame(() => { titleRef?.focus() @@ -646,27 +707,53 @@ export function MessageTimeline(props: {
{ + head = el + setBar("ms", pace(el.clientWidth)) + }} data-session-title classList={{ "sticky top-0 z-30 bg-[linear-gradient(to_bottom,var(--background-stronger)_48px,transparent)]": true, + relative: true, "w-full": true, "pb-4": true, "pl-2 pr-3 md:pl-4 md:pr-3": true, "md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered, }} > + +